Skip to content
This page has been auto-translated and may contain errors.View in English

LLM은 어떻게 작동하는가

docs.scrimba.com

먼저 이름부터 짚어보겠습니다. LLMlarge language model(거대 언어 모델)의 줄임말로, ChatGPT 같은 도구 뒤에 있는 AI, 즉 프롬프트를 보내면 텍스트로 응답을 받는 그 대상을 가리킵니다. 이 장은 그것이 실제로 무엇인지에 대한 작업용 그림을 그려주는 챕터입니다.

질문을 입력하면 몇 초 후 유창하고 자신감 있는 답변이 나타납니다. 마치 모델이 당신을 이해하고 답을 찾아본 것처럼 읽힙니다. 그런 인상은 유용하지만, 중요한 부분에서 틀렸기도 하고, 그 둘 사이의 간극에서 초보자들의 실수가 대부분 나옵니다.

이 장은 앞으로 이 도구들을 다루는 내내 도움이 될 챕터입니다. 모델을 학습시키는 수학을 알아야 모델로 무언가를 만들 수 있는 것은 아닙니다. 하지만 모델이 답을 낼 때 실제로 무슨 일이 벌어지는지에 대한 작업용 그림은 반드시 필요합니다. 그 그림이 나머지 모든 것을 설명해주기 때문입니다. 왜 프롬프팅이 효과가 있는지, 왜 같은 질문에 다른 답이 나오는지, 왜 모델이 사실을 지어내는지, 왜 비용이 그만큼 드는지, 왜 별것 아닐 것 같은 일부 기능이 실제로는 까다로운지까지. 이 핸드북의 이후 모든 내용은 여기서 나오는 아이디어의 결과물입니다.

이 장은 이런 시스템 아래에 있는 사고 모델, 즉 모델이 무엇인지, 어떤 단일 루프를 돌리는지, 그리고 핸드북의 나머지를 설명해주는 몇 가지 속성을 다룹니다. 수학은 전혀 없습니다. 다만 블랙박스를 상대로 코딩하는 것과 모델이 왜 그렇게 동작하는지 알고 코딩하는 것의 차이를 만드는 내용입니다.

이후 장에 나오는 모든 기법, 즉 프롬프팅, 구조화된 출력, 검색, 에이전트는 여기서 읽게 될 내용의 결과물입니다. 이 그림을 제대로 잡으면 나머지는 잡다한 트릭의 모음처럼 느껴지지 않고, 예측 가능한 하나의 기계처럼 느껴지기 시작합니다.

이 모델들은 특정하고 반복 가능한 방식으로 실패하는데, 이 장이 그 이유를 설명합니다. 인터페이스를 걷어내면 언어 모델은 텍스트를 한 조각씩 예측하는 고정된 함수이고, 비결정성, 비용 급증, 컨텍스트 손실, 자신감 있는 오답 등 프로덕션에서 마주치는 거의 모든 실패 유형이 바로 이 하나의 사실에서 나옵니다.

여기에는 새로운 수학이 없고, 그것이 핵심입니다. 지속적인 엔지니어링 우위는 이번 달 유행하는 모델이 아니라 메커니즘 자체에 있습니다. 이후의 모든 의사결정이 딛고 서는 토대로 이 내용을 읽어주십시오.

모델이 실제로 무엇인가

채팅 인터페이스를 걷어내면 거대 언어 모델은 한 가지입니다. 수십억 개의 숫자로 이루어진 고정된 집합, 파라미터(가중치라고도 부릅니다)로 구성된 거대한 수학 함수입니다. 그게 모델의 전부입니다. 데이터베이스도 아니고, 검색 엔진도 아니고, 상자 속에 든 작은 사람도 아닙니다. 텍스트를 입력받아 텍스트를 출력하는, 아주 큰 숫자 더미일 뿐입니다.

그 숫자들은 프로그래머가 하나하나 타이핑해서 만든 것이 아닙니다. 학습(training) 과정에서 설정되었습니다. 제공업체는 모델에게 엄청난 양의 텍스트, 책, 코드, 기사, 웹페이지를 먹였고, 다음 단어를 가리고 모델에게 예측하게 한 뒤 틀릴 때마다 숫자를 살짝 조정하는 간단한 훈련을 수십억 번 반복했습니다. 이를 엄청난 규모로 반복하면 숫자들이 서서히 텍스트 예측에 유리한 값으로 자리 잡습니다. 학습이란 바로 이것입니다. 수십억 개의 다이얼을 예측이 잘될 때까지 조율하는 과정입니다.

여기서 두 가지가 따라오는데, 이 두 가지가 앞으로 마주칠 많은 행동을 설명해줍니다. 첫째, 학습은 미리, 딱 한 번 일어나고 비용이 엄청나게 많이 듭니다. 모델을 호출할 때쯔음이면 숫자들은 이미 고정되어 있습니다. 모델은 당신과의 대화에서 배우지 않습니다. 당신이 하는 말은 모델의 파라미터를 조금도 바꾸지 않습니다. 마치 기억하고 적응하는 것처럼 느껴지지만, 그것은 모델을 감싸고 있는 소프트웨어가 하는 일이며, 모델 자체가 하는 일이 아닙니다. 이 점은 컨텍스트 윈도우를 다루는 절에서 다시 강하게 등장합니다.

둘째, 모델 안에는 사실을 정리한 표 같은 것이 없습니다. "지식"은 만 개의 빵을 구워 본 제빵사가 레시피를 읽지 않고도 손에 기술을 지니고 있는 것처럼, 그 숫자들에 패턴으로 녹아들어 있습니다. 모델은 올바르고 자연스러운 텍스트의 형태를 배웠습니다. 그래서 같은 문장 안에서도 유창하면서 틀릴 수 있는 것입니다. 모델은 저장된 사실을 선반에서 꺼내 읽는 게 아니라, 좋은 답의 형태를 재현하고 있을 뿐입니다.

Juno모델이 실제로 무엇인가 그러니 모델은 텍스트를 텍스트로 바꾸는, 고정된 거대한 숫자 더미이고, 그 숫자들은 학습 과정에서 딱 한 번 조율된 뒤 고정되었습니다. 대화하는 동안 당신에게서 배우지 않고, 안에 깔끔한 사실 표도 없이 패턴만 있습니다. 그래서 자신감 있게 말하면서도 틀릴 수 있으니, 중요한 내용은 항상 확인해야 합니다.

모델은 **순수 함수**입니다. (가중치, 입력)을 넣으면 출력을 계산할 뿐, 그 이상은 없습니다. 가중치는 학습 중에 고정된 수십억 개의 숫자이고, 입력은 당신이 보내는 텍스트입니다. 함수이기 때문에, 같은 설정에서 같은 입력을 넣으면 매번 동일한 기저 점수가 나옵니다. 실행마다 보이는 변화는 이 점수에서 단어를 골라내는 샘플링 단계에서 생기는 것이지, 모델이 마음을 바꾸는 게 아닙니다.

그 함수가 지닌 지식은 손실이 있는 방식으로 저장됩니다. 검색 가능한 레코드로 정리되어 있는 것이 아니라, 가중치 전체에 한꺼번에 뭉개지고 압축되어 있습니다. 읽을 수 있는 행도 없고, 편집할 수 있는 셀도 없습니다. 모델이 "안다"고 하는 사실은 실은 가중치가 필요할 때 재구성해내는 강한 패턴이고, 때로는 살짝 잘못 재구성되기도 합니다. 어떤 사실이 어디에 있는지 짚을 수 없고, 재학습 없이는 사실 하나만 고칠 수도 없습니다.

실무적으로는 모델을 조회하는 지식 베이스가 아니라 호출하는 고정 함수로 다뤄야 합니다. 언어를 다듬고, 텍스트에 대해 추론하고, 지시를 따르는 데는 모델에 의존해도 됩니다. 구체적이거나 최신이거나 검증 가능해야 하는 정보가 필요할 때는 가중치가 기억하고 있으리라 믿지 말고, 그 정보를 직접 입력에 넣어주십시오. 사실을 기대하지 말고 직접 제공한다는 이 한 가지 습관이 자신감 있는 오답을 상당 부분 막아줍니다.

Juno모델이 실제로 무엇인가 모델을 가중치와 입력의 순수 함수라고 생각해보십시오. 같은 입력, 같은 설정이면 같은 기저 점수가 나오고, 실행 간 차이는 샘플링에서 옵니다. 지식은 가중치 전체에 손실 있는 방식으로 압축되어 있어서, 그 안에서 사실 하나를 조회하거나 편집할 수 없습니다. 이에 맞춰 만드십시오. 모델을 호출하는 고정 함수로 다루고, 구체적이거나 최신인 사실은 가중치가 기억하길 바라지 말고 입력을 통해 넣어주십시오.

배포된 모델은 API 뒤에 고정된 가중치입니다. 학습 시점에 고정된 수십억 개의 학습된 숫자가 (가중치, 입력)의 순수 함수를 계산합니다. 그 행동이 실제로 바뀌는 유일한 순간은 제공업체가 다른 모델을 내놓을 때뿐입니다.

여기에는 날카로운 운영상의 함의가 있습니다. 같은 엔드포인트 뒤에서 조용히 이루어진 업데이트가 당신 코드는 한 줄도 바뀌지 않았는데 출력을 바꿔놓을 수 있습니다. 그러니 특정 모델 버전을 고정하고, 그 버전을 계약의 일부로 취급하고, 새 버전을 채택하기 전에 반드시 평가를 다시 돌리십시오. "최신 버전"을 그냥 믿고 채택하는 것이 정상 작동하던 시스템이 조용히 퇴화하는 원인입니다.

가중치는 압축된 파라메트릭 메모리이며, 손실이 있고 검증할 수 없습니다. 버전 간에 모델이 "아는" 것을 비교할 수 없고, 사실을 열거할 수도 없고, 특정 사실이 그 안에 있는지, 맞는지 증명할 수도 없습니다. 그러니 중요한 사실의 정확성을 파라메트릭 회상에 걸지 마십시오. 오답이 비용으로 이어진다면, 당신이 관리하고 인용할 수 있는 소스에서 사실을 가져와 입력에 넣고, 모델은 그 소스에 대해 표현하고 추론하는 역할로만 남기십시오.

가중치를 의도적으로 바꾸는 것이 바로 파인튜닝이 하는 일이며, 그것이 실제로 무엇을 사주는지 명확히 알아둘 필요가 있습니다. 파인튜닝은 행동과 형태, 어조, 형식, 작업 습관을 확실히 바꿔주지만, 신뢰할 만하고 조회 가능한 사실 저장소를 주지는 않습니다. 모델이 응답하는 방식을 다듬으려는 목적으로 사용하고, 믿어야 할 지식을 심는 용도로는 쓰지 마십시오. 사실은 입력에, 스타일은 가중치에 두면 어느 쪽에서도 놀랄 일이 없습니다.

Juno모델이 실제로 무엇인가 가중치는 고정되어 있으니, 행동은 제공업체가 다른 모델을 내놓을 때만 바뀝니다. 버전을 고정하고, 계약의 일부로 삼고, 업그레이드 전에 평가를 다시 돌리십시오. 그렇지 않으면 조용한 교체가 당신이 모르는 사이 성능을 떨어뜨립니다. 파라메트릭 메모리는 손실이 있고 검증할 수 없습니다. 무엇을 아는지 비교할 수 없으니, 중요한 사실은 회상이 아니라 당신이 관리하는 소스에서 가져와 입력에 넣으십시오. 그리고 파인튜닝은 행동과 형태를 바꿀 뿐 믿을 만한 사실 저장소가 아니니, 모델이 어떻게 응답할지를 위해 쓰고 무엇을 알아야 하는지를 위해 쓰지는 마십시오.

다음 토큰 예측하기

그 숫자 더미가 갖춰지면, 모델은 정확히 한 가지 일을 합니다. 다음 텍스트 조각을 계속 반복해서 예측하는 것입니다.

이 루프를 천천히 풀어보겠습니다. 당신이 텍스트를 넘겨줍니다. 모델은 그 텍스트를 모든 파라미터를 거쳐 실행하고, 가능한 다음 조각 각각에 대한 확률을 만들어냅니다. 알고 있는 수만 개의 후보 전체에 대해 동시에요. "The capital of France is"(프랑스의 수도는) 다음에는 "Paris"(파리)라는 조각이 97%, "a"가 1%, 나머지는 거의 0에 가까운 점수를 받을 수 있습니다.

그러면 모델은 조각 하나를 골라 텍스트에 이어붙이고, 다음 조각을 고르기 위해 전체 과정을 다시 실행합니다. 답이 완성될 때까지 이를 반복합니다.

이게 이 엔진의 전부입니다. 당신이 하나의 생각처럼 읽는 답변은 사실 한 번에 하나씩 만들어진 조각들이 모여서 된 것이고, 각 조각은 그 순간 새로 계산된 확률 순위에서 골라진 것이며, "다음에 뭐가 그럴듯한가"를 넘어서는 문장 전체에 대한 계획은 전혀 없습니다. 당신이 사용하게 될 모든 능력, 채팅, 코딩, 번역, 도구 사용, 추론은 모두 이 똑같은 루프가 아래에서 돌아가고 있는 것입니다.

다음 단어를 제안해주는 휴대폰 키보드도 같은 원리를 축소한 것입니다. 다른 점은 그 추측 뒤에 있는 수십억 개의 파라미터입니다. 충분히 많은 텍스트로 학습시키면, "가장 그럴듯한 다음 조각이 무엇인가"라는 원리만으로도 작동하는 코드와 명확한 설명을 써낼 만큼 충분히 좋아집니다. 하지만 그 기저의 작동 방식은 절대 바뀌지 않습니다. 이것은 예측이지, 이해가 아니며, 저장된 답을 찾아보는 것도 아닙니다.

Juno다음 토큰 예측하기 전체 엔진은 하나의 작은 루프입니다. 지금까지의 텍스트를 읽고, 가능한 다음 조각 각각에 점수를 매기고, 하나를 고르고, 답이 끝날 때까지 이를 반복하는 것입니다. 모델은 "다음에 뭐가 그럴듯한가"를 넘어서는 문장 계획이 없으니, 당신이 하나의 생각처럼 읽는 답변도 실제로는 한 번에 하나씩 만들어진 것입니다. 그것은 예측이지, 이해가 아니고, 저장된 답을 조회하는 것도 아닙니다.

모델은 한 가지 일을 반복합니다. 각 단계마다 지금까지의 모든 텍스트를 읽고, 어휘집에 있는 모든 토큰(토큰은 텍스트 조각으로, 단어의 일부인 경우가 많습니다)에 대해 점수를 하나씩 냅니다. 한 번에 수만 개씩요. 그 점수들이 확률 분포가 되고, 모델은 그 분포에서 토큰 하나를 샘플링합니다. 그런 다음 그 토큰을 텍스트에 이어붙이고 다음 토큰을 고르기 위해 전체 과정을 다시 실행합니다. "The capital of France is" 다음이라면 "Paris"가 확률의 거의 전부를 차지하고 나머지는 거의 없을 수 있습니다.

이렇게 결과를 다시 입력으로 되먹이는 방식 때문에 **자기회귀(autoregressive)**라고 부릅니다. 새로운 토큰마다 지금까지의 모든 텍스트, 즉 이번 턴에서 이미 써낸 토큰들까지 포함해서 그것들에 의존합니다. 전체적인 계획도 없고, 다시 고칠 수 있는 초안도 없습니다. 모델은 한 번에 토큰 하나씩 확정하고, 토큰을 되돌릴 수 없기 때문에 스스로를 궁지에 몰아넣을 수 있습니다. 자신감 있게 내놓은 초반 단어 하나가 나머지 문장을 어색한 방향으로 밀어붙여서, 결국 그 어색한 문장을 끝까지 완성해야 하는 상황이 생길 수 있습니다.

이 한 가지 사실이 실무에서 마주치게 될 몇 가지를 설명해줍니다. 모델이 최종 답변을 내기 전에 추론 과정을 써보게 하는 것이 도움이 되는 이유는, 토큰이 더 많다는 것은 답을 확정하기 전에 문제에 더 많은 단계와 더 많은 계산을 쏟는다는 뜻이기 때문입니다. 출력의 구조는 엄격하게 왼쪽에서 오른쪽으로 결정되므로, 응답의 형태는 미리 계획되는 것이 아니라 진행되면서 정해집니다. 그리고 같은 루프가 도구 사용을 이끄는 원리이기도 합니다. 모델은 호출을 구성하는 토큰들을 예측하고, 당신이 그것을 실행하면, 그 결과가 다음 예측을 위한 추가 텍스트로 다시 되먹여집니다.

Juno다음 토큰 예측하기 각 단계마다 모델은 어휘집의 모든 토큰에 점수를 매기고, 하나를 샘플링하고, 그것을 다시 입력에 되먹이고, 이를 반복합니다. 이 되먹임 루프가 바로 자기회귀라는 뜻입니다. 계획도 없고 되돌리기도 없으니, 토큰 단위로 확정하다가 스스로 궁지에 몰릴 수 있습니다. 그래서 답 전에 추론을 써보게 하면 도움이 되고, 도구 사용도 결과를 되먹이는 같은 루프입니다.

생성은 구조적으로 순차적입니다. 모델은 하나의 토큰(텍스트 조각)을 만들고, 그것을 되먹이고, 다음 것을 예측합니다. 그래서 출력 토큰 하나마다 네트워크를 거치는 별도의 순전파가 필요합니다. 입력을 읽는 것은 상대적으로 저렴해서 한 번에 처리되지만, 느리고 비용이 많이 드는 부분은 모델이 글을 쓰는 과정입니다. 지연 시간과 비용은 입력 길이가 아니라 출력 길이에 따라 확장됩니다. 여기서 실무적인 지렛대가 나옵니다. 출력을 짧게 유지하고, 사용자가 전체 응답을 기다리지 않고 진행 상황을 볼 수 있도록 토큰을 스트리밍하고, 버릴 장식적인 포맷팅에 돈을 쓰지 마십시오.

또 다른 결과는 정확성 문제입니다. 전체적인 계획이 없기 때문에 한 번의 패스는 자기 교정이 이루어지지 않습니다. 모델은 왼쪽에서 오른쪽으로 확정하고 이미 나온 토큰을 수정할 수 없습니다. 그래서 정확해야 하는 것에는 검증이나 초안에 대한 두 번째 패스가 필요하고, 답변 전에 추론을 써내는 모델이 대체로 더 나은 이유도 여기에 있습니다. 확정하기 전에 더 많은 단계를 사주는 셈입니다. 첫 번째 생성은 판결이 아니라 초안으로 다뤄야 합니다.

한 가지 활용할 만한 지렛대가 있습니다. 많은 설정에서 토큰별 확률(logprobs, 모델이 고른 각 토큰에 대한 자체 확신도 점수)을 노출합니다. 이를 읽어서 확신도가 낮은 구간을 표시하거나, 모델이 확신하지 못했던 답을 게이트로 걸러내거나, 불확실한 경우를 두 번째 패스나 사람에게 넘길 수 있습니다. 이미 갖고 있는 값싼 신호이며, "모델이 여기서 틀렸을 수도 있다"는 것을 추측이 아니라 프로그래밍적으로 대응할 수 있는 것으로 바꿔줍니다.

Juno다음 토큰 예측하기 출력 토큰 하나마다 그 자체로 순전파이므로, 비용과 지연 시간은 입력이 아니라 출력 길이를 따라갑니다. 응답을 짧게 유지하고, 스트리밍하고, 버릴 포맷팅에 돈을 쓰지 마십시오. 계획도 되돌리기도 없으니 한 번의 패스는 자기 교정을 하지 않습니다. 첫 생성을 초안으로 다루고, 정확해야 하는 것에는 검증 패스를 추가하십시오. 토큰별 logprobs를 읽을 수 있다면, 모델이 가장 확신하지 못한 구간을 표시하거나 넘기는 데 쓰십시오. 이미 갖고 있고 잘 쓰지 않는 확신도 신호입니다.

무작위성 다이얼

모델이 항상 점수가 가장 높은 조각 하나만 골랐다면, 같은 프롬프트에 매번 같은 답을 내놓았을 것이고, 그 답은 대체로 밋밋하고 반복적이었을 것입니다. 그래서 대부분의 경우 선택에는 약간의 무작위성이 들어갑니다. 대개 확률이 높은 조각을 고르지만, 항상 최상위 것만 고르지는 않습니다. 그래서 정확히 같은 질문을 두 번 해도 다른 답변을 받을 수 있는 것입니다. 이것은 시스템이 설계대로 작동하는 것이지, 오류가 아닙니다.

이 무작위성의 정도는 **temperature(온도)**라는 설정으로 조절하며, 코드로 모델 호출하기 챕터에서 모델을 호출할 때 설정하게 됩니다. 지금 그 직관을 미리 가져보는 것이 좋습니다. temperature가 낮으면 모델은 상위권 선택지에 가까이 붙습니다. 집중되고, 일관되고, 예측 가능한데, 이는 인보이스에서 숫자를 뽑아내거나 텍스트를 분류할 때 바라는 특성입니다. temperature가 높으면 확률이 낮은 조각들도 더 자주 선택됩니다. 더 다양하고 창의적이지만, 틀린 방향으로 헤맬 가능성도 더 높습니다. 브레인스토밍과 창작 글쓰기는 높은 쪽을 원하고, 정답이 있는 작업은 낮은 쪽을 원합니다.

그러니 하나의 다이얼이 모델을 "신중하고 반복 가능한" 상태와 "놀랍고 창의적인" 상태 사이에서 움직입니다. 어느 쪽이든 예측 루프는 똑같습니다. temperature는 모델이 가장 안전한 추측을 넘어서 얼마나 대담하게 손을 뻗을지만 바꿉니다.

Juno무작위성 다이얼 temperature는 당신의 무작위성 다이얼입니다. 추출이나 분류처럼 정답이 있는 작업에는 낮게 설정하면 집중되고 반복 가능한 답을 얻습니다. 브레인스토밍과 창작 작업에는 높게 설정하면 다양성과 함께 헤맬 가능성도 커집니다. 예측 루프는 같고, 그저 손을 더 대담하게 뻗느냐 안전하게 뻗느냐의 차이입니다.

모델이 다음 조각을 고르기 전에는 모든 후보에 대한 확률이 존재합니다. temperature는 모델이 그 분포에서 샘플링하기 전에 분포의 형태를 바꿉니다. temperature가 낮으면 분포가 가장 가능성 높은 단일 토큰 쪽으로 뾰족해지고(늘 최상위 선택지를 고르는 것에 가까워지고), 그래서 출력이 집중되고 반복 가능해집니다. temperature가 높으면 분포가 평평해져서 가능성이 낮은 토큰에도 실제 기회가 생기고, 그래서 출력이 더 다양해지고 틀린 방향으로 흘러갈 가능성도 커집니다. 같은 프롬프트가 다른 답을 낼 수 있는 이유가 바로 이것입니다. 매번 뽑기가 벌어지고 있고, temperature가 그 확률이 얼마나 기울어져 있는지를 정합니다.

temperature가 여기서 유일한 조절 장치는 아닙니다. Top-p(확률을 합쳐서 p가 될 만큼의 가장 작은 토큰 집합에서만 샘플링)와 top-k(가장 가능성 높은 k개의 토큰만)는 둘 다 샘플링 전에 후보군을 잘라냅니다. 이들은 효과 면에서 temperature와 겹치므로, 세 개의 독립된 설정이 아니라 하나의 무작위성 설정으로 생각하는 것이 좋습니다.

실무적으로는 어떤 기본값이 딸려오든 그것을 믿지 말고, 작업별로 temperature를 명시적으로 설정하는 것입니다. 추출, 분류, 그리고 나중에 파싱할 구조화된 출력을 내는 작업에는 낮게, 다양성이 목적인 아이디어 발상과 초안 작성에는 높게 설정하십시오. 상호작용에 주의하십시오. 높은 temperature와 top-p 1을 함께 쌓으면 확률 분포의 꼬리 전체가 살아 있게 되는데, 이는 신뢰할 만하고 파싱 가능한 결과가 필요할 때는 오히려 불리하게 작용합니다.

Juno무작위성 다이얼 temperature는 샘플링 전에 확률 분포의 형태를 바꿉니다. 낮으면 최상위 토큰 쪽으로 뾰족해져서 집중된 출력이 나오고, 높으면 평평해져서 다양성이 커집니다. temperature, top_p, top_k를 세 개가 아니라 하나의 무작위성 설정으로 다루십시오. 작업별로 설정하십시오. 추출, 분류, 구조화된 출력에는 낮게, 아이디어 발상에는 높게. 파싱 가능한 결과가 필요할 때는 높은 temperature와 top_p 1을 함께 쓰지 마십시오.

temperature는 샘플링 전에 점수 분포를 조정하며, 유혹적인 지름길은 이를 0으로 설정하고 출력을 결정론적이라고 부르는 것입니다. 거의 그렇지만, 보장은 아닙니다. 부동소수점 연산, 서버 측 배치 처리, 대형 모델 내부의 라우팅이 0에서도 작은 비결정성을 끌어들입니다. 그러니 같은 프롬프트를 반복했을 때 바이트 단위로 동일한 출력을 가정하는 정확 일치 캐싱이나 정확 일치 테스트를 만들지 마십시오. 그 가정은 간헐적으로 실패하고, 간헐적인 실패는 추적하기 비싼 종류의 문제입니다.

이것이 샘플링된 동작을 테스트하는 방식을 결정합니다. 단 한 번의 실행은 확률적(stochastic) 시스템(실행마다 출력이 달라지는 시스템)에 대해 거의 아무것도 말해주지 않습니다. 그러니 프롬프트를 여러 번 실행해 출력의 분포를 보십시오. 얼마나 자주 스펙에 맞게 나오는지, 편차가 얼마나 큰지, 꼬리 쪽에서 어떻게 틀어지는지를 보는 것입니다. 그것이 당신의 평가(evals)가 하는 일이며, 한 번의 운 좋은 통과가 아니라 퍼짐을 볼 수 있을 만큼의 샘플을 원합니다.

temperature, top-p(누적 확률 p에 도달하는 가장 작은 토큰 집합에서만 샘플링), top-k(가장 가능성 높은 k개의 토큰만 유지)를 반사적으로 각각 설정하는 세 개의 다이얼이 아니라, 함께 조율하는 하나의 샘플링 예산으로 다루십시오. 그 작업에 여전히 받아들일 만한 다양성을 주는 가장 낮은 무작위성을 고르십시오. 셋을 동시에 조이면 출력이 반복적이고 부서지기 쉬운 것으로 무너질 수 있고, 셋을 모두 열어두면 나중에 검증해야 할 편차를 안게 됩니다. 양쪽 다 실제 비용이 드니, 의도적으로 소비하십시오.

Juno무작위성 다이얼 temperature 0은 거의 결정론적이지만 보장은 아닙니다. 부동소수점, 배치 처리, 내부 라우팅이 비결정성을 흘려보내니, 정확 일치 캐싱과 정확 일치 테스트는 하지 마십시오. 샘플링된 동작은 여러 번 실행해서 분포를 읽으십시오. 한 번의 패스가 아니라요. 그것이 평가가 존재하는 이유입니다. temperature, top_p, top_k를 하나의 샘플링 예산으로 조율하고, 여전히 받아들일 만한 다양성을 주는 가장 낮은 무작위성을 고르십시오.

토큰

모델이 예측하는 "조각"에는 이름이 있습니다. **토큰**입니다. 토큰은 텍스트 한 조각으로, 온전한 단어인 경우가 많고, 때로는 단어의 일부에 불과합니다. 흔한 단어는 보통 하나의 토큰이고, 더 길거나 드문 단어는 여러 개로 쪼개집니다.

"tokenization"  ->  "token" + "ization"
"unbelievable"  ->  "un" + "believ" + "able"
"cat"           ->  "cat"

왜 온전한 단어나 개별 글자 대신 이런 이상한 조각으로 텍스트를 잘라내는 걸까요? 여기에는 트레이드오프가 있습니다. 개별 글자를 쓰면 모든 시퀀스가 엄청나게 길어질 텐데, 길이는 곧 모델이 계산해야 할 양입니다. 온전한 단어를 쓰면 수백만 개 항목의 어휘집이 필요할 텐데, 그마저도 누군가 새 단어를 만들거나 오타를 내는 순간 바로 깨질 것입니다. 토큰은 그 중간 길입니다. 몇만 개 정도의 흔한 조각으로 이루어진 고정된 어휘집이며, 이를 조합하면 모델이 한 번도 본 적 없는 단어까지도 무엇이든 표현할 수 있습니다.

여기서 사람들을 놀라게 하는, 이면에 숨은 결과가 하나 있습니다. 모델은 글자를 결코 보지 않습니다. 모델이 보는 것은 토큰이고, 모델에게 그것은 사실 그 조각들을 가리키는 ID 번호일 뿐입니다. 그래서 우리에게는 힘들이지 않고 되는 것처럼 보이는 작업이 모델에게는 어려울 수 있습니다. 단어 속 글자 수 세기, 문자열 뒤집기, "strawberry"에 r이 세 개 있다는 것을 알아채는 것 같은 작업들입니다.

모델이 멍청해서 그런 게 아닙니다. 마치 다 구워진 빵 한 덩어리에서 개별 밀가루 낱알을 맛으로 구분할 수 없는 것처럼, 모델은 개별 글자를 아예 인식하지 못합니다. 모델이 맞춤법이나 글자 세기 작업에서 헛발질을 할 때, 그 이유가 바로 이것이며, 대개는 그 부분을 일반 코드로 처리하는 것이 해결책입니다.

토큰은 또한 당신이 늘 신경 써야 할 두 가지의 단위이기도 합니다.

  • 비용: 제공업체는 토큰 단위로 요금을 매기는데, 당신이 보내는 토큰과 모델이 다시 써주는 토큰 모두를 계산합니다. 그래서 긴 문서를 입력하는 것과 긴 답을 받는 것 모두 비용을 더 들게 합니다. 영어에 대한 대략적인 감으로는 토큰당 약 4글자, 또는 75단어당 100토큰 정도입니다. 코드와 다른 언어는 흔히 더 많은 토큰으로 쪼개지는데, 이것이 그것들이 더 비쌀 수 있는 이유의 일부입니다. 코드로 모델 호출하기를 시작하면 이를 직접 마주하게 됩니다.
  • 한계: 모델이 한 번에 다룰 수 있는 토큰 수에는 상한선이 있는데, 이것이 다음 절의 내용입니다.
Juno토큰 토큰은 텍스트 조각으로, 보통 단어 또는 그 일부이며, 모델은 그 조각들을 글자가 아니라 ID 번호로 읽습니다. 그래서 세기나 맞춤법에서 헛발질할 수 있으니, 그런 작업은 일반 코드에 맡기십시오. 그리고 양쪽 방향 모두 토큰 단위로 비용을 내고, 한 번에 들어갈 수 있는 개수에 상한이 있으니, 토큰이 적을수록 비용은 줄고 여유는 늘어납니다.

모델은 토큰을 예측합니다. 온전한 단어인 경우가 많고 때로는 조각인 텍스트 부분입니다. 이 분할을 담당하는 것은 **토크나이저(tokenizer)**이고, 이는 가장 흔한 문자 쌍을 반복적으로 합쳐서 몇만 개 정도의 고정된 조각 집합을 만드는 방식으로 학습된 어휘집을 바탕으로 작동합니다(보통 바이트 페어 인코딩, 즉 BPE라는 방식입니다). 흔한 단어가 하나의 토큰으로 남고 드문 단어는 여러 조각으로 쪼개지는 이유가 바로 이 이력입니다. 흔한 패턴은 학습 중에 합쳐졌고, 흔하지 않은 패턴은 그러지 않았습니다.

여기서 두 가지 실무적인 사실이 나옵니다. 첫째, 모델은 글자를 다루지 않고 토큰 ID만 다루기 때문에, 글자 단위 연산(글자 세기, 문자열 뒤집기, 정확한 위치에서 자르기)은 프롬프트 안에서는 불안정하고 당신의 코드 안에서는 안정적입니다. 그런 작업은 코드로 넘기고, 모델에게는 언어 부분을 맡기십시오. 둘째, 토큰화는 모델별로 다르므로 토큰 수는 이식되지 않습니다. 같은 문장이 다른 토크나이저에서는 다른 수의 토큰이 될 수 있는데, 이는 비용을 산정하거나 콘텐츠를 윈도우에 맞추는 순간 중요해집니다.

만들 때 얻는 교훈: 글자 수로 토큰 수를 대충 짐작하지 마십시오. 모델이 실제로 쓰는 토크나이저로 세십시오. 그것이 당신이 요금을 매기고 제한을 받는 숫자이기 때문입니다. 코드로 모델 호출하기를 시작하면 이 모든 것을 구체적으로 마주하게 됩니다. 거기서는 보내는 토큰 수와 받는 토큰 수를 합친 것이 요금이 됩니다.

Juno토큰 토크나이저는 학습된 어휘집(BPE)을 이용해 텍스트를 쪼개는데, 그래서 흔한 단어는 하나의 토큰이고 드문 단어는 조각난 여러 토큰이 됩니다. 토큰화는 모델별로 다르므로, 글자 수로 짐작하지 말고 모델 고유의 토크나이저로 세십시오. 그리고 글자 단위 작업(세기, 뒤집기, 정확한 슬라이싱)은 프롬프트가 아니라 코드에 두십시오.

토큰은 당신이 배포하는 모든 것의 계량기입니다. 토큰은 토크나이저가 학습한 어휘집에서 온 텍스트 조각이며, 토큰당 요금과 고정된 컨텍스트 상한이 당신의 비용과 설계를 결정하는 두 가지 수치입니다. 위안이 되는 "토큰당 4글자" 어림 규칙은 평범한 영어 문장에서는 맞지만, 프로덕션이 실제로 존재하는 곳, 즉 코드, JSON, 깊이 중첩된 포맷, 비라틴 문자 체계에서는 정확히 무너집니다. 이들은 모두 그 어림 규칙이 예측하는 것보다 훨씬 많은 토큰으로 쪼개집니다. 그러니 어림값이 아니라 실제 토크나이저로 예산을 잡으십시오. 그렇지 않으면 비용과 용량 추정치가 정확히 당신이 실제로 보내는 페이로드에서 틀리게 됩니다.

이 조각화는 균등하게 분포되어 있지 않고, 이는 규모가 커지면 공정성과 비용의 문제가 됩니다. 비라틴 언어로 된 같은 의미가 영어 버전보다 몇 배나 많은 토큰을 들 수 있습니다. 그래서 같은 기능이 어떤 사용자에게는 조용히 더 비싸고 더 느려지는 것입니다. 누군가에게 균일 가격이나 고정 지연 시간을 약속하기 전에 언어별로 측정해볼 가치가 있습니다.

현장에서 얻은 두 가지 습관이 더 있습니다. 형식도 지출입니다. 공백, 장황한 구분자, 장식적인 마크다운은 의미를 하나도 더해주지 않으면서 토큰을 소모합니다. 그러니 모델에 넘기는 구조는 간결하게 유지하고, 표현은 나중에 당신의 코드가 추가하도록 하십시오. 그리고 깨끗하고 정상적인 텍스트를 넘기십시오. 드물거나 깨진 토큰(글자 깨짐, 낯선 제어 문자, 이상한 인코딩)은 어휘집의 얇고 충분히 학습되지 않은 구석에 자리하고 있어서 이상한 출력을 유발할 수 있기 때문입니다. 이 중 어느 것도 특별한 것이 아닙니다. 믿을 수 있는 토큰 예산과 청구서에서 놀라게 되는 예산의 차이가 바로 여기서 갈립니다.

Juno토큰 토큰당 4글자 규칙은 코드, JSON, 비라틴 문자에서 무너지는데, 그곳이 바로 당신의 실제 트래픽이 존재하는 곳입니다. 그러니 실제 토크나이저로 예산을 잡으십시오. 비라틴 언어는 같은 의미에 몇 배나 많은 토큰이 들 수 있는데, 언어별로 측정해볼 가치가 있는 실제 비용과 공정성 문제입니다. 장식적인 포맷을 걷어내 토큰을 아끼고, 깨끗한 텍스트를 넘겨서 드물거나 깨진 토큰이 이상한 동작을 사지 않게 하십시오.

컨텍스트 윈도우

**컨텍스트 윈도우**는 모델이 한 번에 받아들일 수 있는 텍스트의 양으로, 토큰 단위로 측정됩니다. 모델의 책상 크기라고 생각해보십시오. 하나의 요청에 관련된 모든 것, 즉 당신의 지시사항, 지금까지의 대화, 붙여넣은 문서들, 그리고 모델이 작성하고 있는 답변까지 모두 그 책상 위에 한꺼번에 놓여야 합니다.

왜 이런 한계가 존재할까요? 예측이 작동하는 방식에서 비롯됩니다. 다음 토큰을 고르기 위해 모델은 입력의 모든 토큰이 다른 모든 토큰과 어떤 관계인지를 가늠합니다. 그래야 "그것"이 세 문장 전에 나온 고양이를 가리킨다는 것을 알 수 있습니다. 이런 전체 대 전체 비교가 모델이 한 문단 전체에 걸쳐 의미를 추적하게 해주는 힘이지만, 동시에 긴 입력을 처리하는 비용이 커지는 원인이기도 합니다. 제공업체는 각 요청을 다룰 만한 크기로 유지하기 위해 상한선, 즉 컨텍스트 윈도우를 설정합니다.

책상이 가득 차면 뭔가는 치워야 합니다. 대화가 윈도우를 넘칠 만큼 길어지면, 가장 앞부분이 떨어져 나가고 모델은 더 이상 그것을 볼 수 없습니다. 이것이 긴 채팅이 처음에 당신이 말한 것을 "잊어버린" 것처럼 보이는 이유입니다. 인간적인 의미에서 잊어버린 것이 아니라, 그 토큰들이 책상 밖으로 밀려난 것입니다. 또한 모델은 책상 위에 있는 것 중 시작 부분과 끝부분에 가장 주의를 기울이는 경향이 있고, 중간에 묻힌 내용은 놓치기 쉽다는 점도 알아두면 좋습니다. 그러니 윈도우를 채울 때 중요한 텍스트를 어디에 두는지가 중요합니다.

이제 거의 모든 초보자를 붙잡는 핵심을 짚어보겠습니다. 나머지보다 더 중요한 내용입니다. 모델은 별개의 요청 사이에 아무 기억도 갖지 않습니다. 모델의 숫자는 고정되어 있다는 것을 기억하십시오. 각 호출은 빈 책상에서 시작합니다. 모델은 당신의 지난 질문, 당신의 이름, 1분 전의 어떤 것도 기억하지 못합니다.

채팅 앱이 하나의 흐르는 대화처럼 느껴지는 것은, 그 앱이 새 메시지를 보낼 때마다 전체 대화 이력을 조용히 다시 보내주기 때문입니다. 그 연속성은 모델을 감싸고 있는 소프트웨어가 만들어내는 것이며, 모델 자체가 지닌 것이 아닙니다.

이 한 가지 사실이 여기서부터 무엇을 만들든 그 방식을 결정합니다. 모델이 무언가를 알기를 원한다면, 그 요청 안의 책상 위에 그것을 올려놔야 합니다. 지금까지의 대화, 사용자의 세부 정보, 관련 문서까지, 매번 전부 다요. 이 핸드북의 상당 부분은 사실 그 한 가지 일을 잘하는 방법, 즉 올바른 텍스트를 책상 위에 올려놓는 방법에 관한 것입니다.

Juno컨텍스트 윈도우 컨텍스트 윈도우는 모델의 책상입니다. 모든 요청은 그 위에 담겨야 하고, 넘치면 가장 오래된 텍스트가 떨어져 나갑니다. 모델은 호출 사이에 아무 기억도 갖지 않으므로, 앱이 매번 전체 이력을 다시 보내서 마치 하나의 이어진 대화인 것처럼 흉내 냅니다. 여기서부터 당신의 일은 매 요청마다 올바른 텍스트를 그 책상 위에 올려놓는 것입니다.

컨텍스트 윈도우를 하나의 요청이 쓸 수 있는 고정된 토큰 예산으로 생각해보십시오. 여기에는 당신의 지시사항, 진행 중인 대화, 붙여넣은 문서, 그리고 생성되고 있는 답변이 모두 포함됩니다. 이 윈도우가 존재하는 이유는 모델이 입력을 읽는 방식 때문입니다. **어텐션(attention)**이라는 메커니즘이 모든 토큰을 다른 모든 토큰과 비교해서 무엇이 무엇과 관련되는지 계산합니다. 이 비교 작업 덕분에 모델은 "그것"을 그것이 지칭하는 명사로 되돌려 연결할 수 있고, 그 비용은 대략 입력 길이의 제곱에 비례해 커집니다. 토큰을 두 배로 늘리면 작업량은 두 배 넘게 늘어나는데, 이것이 긴 입력이 느리고 비용이 많이 드는 진짜 이유입니다.

윈도우 안에 들어간다는 것과 모델이 그 안의 모든 것을 실제로 활용한다는 것은 별개입니다. **잃어버린 중간(lost in the middle)**이라고 부르는, 잘 문서화된 효과가 있습니다. 모델은 입력의 시작과 끝에는 가장 안정적으로 주의를 기울이지만, 중앙에 놓인 자료는 훑고 지나갈 수 있습니다. 그러니 가장 중요한 지시사항과 데이터는 붙여넣은 텍스트 더미에 묻어두지 말고 프롬프트의 위쪽이나 아래쪽에 배치하십시오.

이것의 또 다른 절반은 기억이며, 핵심 사실은 모델이 **상태를 갖지 않는다(stateless)**는 것입니다. 별개의 요청 사이에는 아무것도 기억하지 못합니다. 각 호출은 빈 윈도우로 시작하므로, 모델이 지금까지의 대화를 아는 유일한 방법은 당신이 매번 그것을 다시 보내주는 것입니다. 이력을 다시 보내고 그것을 다듬는 것은 모델의 일이 아니라 당신의 일입니다. 채팅이 윈도우를 넘어서 커지면, 오래된 턴을 잘라내거나 짧은 요약으로 대체하는 식으로 직접 관리해서, 남기는 토큰이 여전히 중요한 토큰이 되도록 하십시오.

이것이 컨텍스트 구성을 이 모델들로 무언가를 만드는 작업의 중심에 놓습니다. 윈도우에 무엇을, 어떤 순서로 넣고, 이전 내용을 어떻게 압축할지 결정하는 것이 작업의 대부분이며, 프롬프팅이 그것을 의도적으로 다듬는 법을 배우는 곳입니다.

Juno컨텍스트 윈도우 윈도우는 지시사항, 이력, 문서, 답변을 아우르는 토큰 예산이며, 어텐션 때문에 긴 입력은 대략 길이의 제곱에 비례하는 비용이 듭니다. 모델은 잃어버린 중간 효과를 겪으니, 중요한 것은 시작이나 끝 근처에 두십시오. 모델은 상태를 갖지 않으므로, 대화 이력을 다시 보내고 다듬는 것은 매 호출마다 당신의 몫입니다.

컨텍스트 윈도우를 목표가 아니라 상한선으로 다루십시오. 이는 하나의 요청이 받는 토큰 예산이며, 당신의 지시사항, 진행 중인 이력, 검색된 문서, 출력까지 걸쳐 있습니다.

여기에 넣는 토큰 하나하나가 돈과 지연 시간이 드는데, 어텐션 단계(모델이 입력을 읽기 위해 실행하는 전체 대 전체 토큰 비교)가 대략 길이의 제곱에 비례해 확장되기 때문입니다. 더 나쁜 것은, 유효한 회상 능력이 한계에 도달하기 훨씬 전부터 저하된다는 점입니다. 잃어버린 중간 효과는 중앙에 묻힌 자료가 기술적으로는 들어맞더라도 정확도가 떨어진다는 뜻입니다. 그러니 모든 것을 쏟아붓지 말고 컨텍스트를 골라내십시오. 그리고 원본 데이터가 클 때는 전체 말뭉치를 붙여넣지 말고 RAG로 관련된 조각만 검색하십시오.

순서는 세부사항이 아니라 지렛대입니다. 제공업체는 **프롬프트 캐싱(prompt caching)**을 통해 바뀌지 않은 앞부분에 대한 계산을 재사용할 수 있는데, 이때 캐시되는 내부 상태가 KV 캐시, 즉 모델이 이미 읽은 토큰에 대해 저장해둔 작업 결과입니다. 안정적인 콘텐츠(시스템 지시사항, 고정된 참조 자료)를 앞에 두고 가변적인 콘텐츠를 뒤에 두면, 프롬프트 앞부분이 캐시에서 제공되기 때문에 반복 호출이 더 싸고 빨라집니다. 이 앞부분을 부주의하게 재배열하면 캐시가 무효화되고 다시 전체 비용을 치르게 됩니다.

턴을 넘나드는 기억은 모델이 지닌 기능이 아니라 당신이 만드는 엔지니어링 선택이며, 각 방식은 저마다의 방식으로 실패합니다.

  • 단순한 윈도잉(windowing), 즉 가장 최근 턴만 유지하는 방식은 저렴하지만 사용자가 여전히 당신이 알고 있으리라 기대하는 초기 사실들을 조용히 떨어뜨립니다.
  • **요약(summarisation)**은 더 적은 토큰으로 더 많은 이력을 담지만, 디테일을 잃고 자기 자신의 오류를 조용히 인코딩할 수 있습니다.
  • **검색(retrieval)**은 관련된 것만 다시 가져오지만, 당신의 인덱스가 올바른 조각을 반환하는지에 전적으로 의존합니다.

사용 사례별로 선택하고, 어느 것을 고르든 결국 잘못된 것을 표면화시킬 것이라고 가정하십시오. 믿기보다는 그것을 계측하십시오.

Juno컨텍스트 윈도우 윈도우는 목표가 아니라 상한선입니다. 토큰마다 비용과 지연 시간이 오르고 한계에 도달하기 전부터 회상이 썩어가니, 골라내고 큰 것은 검색에 맡기십시오. 안정적인 콘텐츠는 앞에, 가변적인 것은 뒤에 두어야 프롬프트 캐싱(KV 캐시)이 실제로 효과를 냅니다. 턴 간 기억은 윈도잉, 요약, 검색 중에서 고르는 당신의 설계 선택이고, 저는 셋 다에게 발등을 찍혀봤으니 무엇을 고르든 계측하십시오.

모델이 모르는 것

모델의 모든 지식은 학습 과정에서 스며들었으므로, 학습 데이터에 있던 것만 알고 있고, 그 데이터는 과거의 어느 마감 시점까지만 수집되었습니다. 여기서 두 가지 한계가 곧바로 따라옵니다.

첫째, 마감 시점 이후에 일어난 일은 전혀 모릅니다. 지난주 뉴스나 이번 달에 나온 라이브러리를 물어보면 제대로 알 도리가 없지만, 그럼에도 그럴듯해 보이는 답을 신나게 내놓을 수도 있습니다. 둘째, 사적인 것은 애초에 알 수가 없었습니다. 당신 회사의 내부 문서, 사용자의 주문 내역, 데이터베이스의 내용 같은 것은 그것이 학습한 공개 텍스트에 없었으므로, 아무리 곱게 물어봐도 알 수 없습니다.

더 조용한 한계도 있습니다. 모델은 대개 자신이 "아는" 것이 어디서 왔는지 말해주지 못합니다. 지식이 출처가 표기된 별개의 사실로 정리되어 있는 것이 아니라 내부에서 뒤섞여 있기 때문입니다. 인용을 만들어낼 수는 있지만, 그것은 실제 인용을 찾아보는 것이 아니라 인용이 어떻게 보여야 하는지를 예측하는 것입니다.

이것이 나중에 배울 검색, 즉 RAG라는 기법이 필요한 이유입니다. 최근 사건이나 당신의 사적인 데이터에 대해 모델이 답하게 하고 싶다면, 재학습을 시키지 않습니다. 관련 텍스트를 직접 가져와서 요청 시점의 책상 위에 놓아, 모델이 추측하는 대신 눈앞의 사실을 보게 하는 것입니다. 모델은 신뢰할 만한 구체적 사실의 우물이라기보다는, 당신이 준 텍스트에 대해 추론하는 존재에 훨씬 가깝습니다.

Juno모델이 모르는 것 모델은 과거의 어느 마감 시점까지의 학습 데이터에 있던 것만 알기 때문에, 최근 사건과 사적인 것은 놓치고, 그 사실이 어디서 왔는지도 믿을 만하게 말해주지 못합니다. 최신이거나 사적인 정보가 필요할 때는 재학습시키지 말고, 관련 텍스트를 가져와서 요청 안에서 모델에게 건네주십시오. 모델을 신뢰할 만한 기억이 아니라 당신이 준 것에 대해 추론하는 존재로 다루십시오.

모델에 내재된 지식, 즉 파라메트릭 지식(parametric knowledge)(가중치, 즉 학습 중에 설정된 조율 가능한 숫자에 인코딩된 사실)은 학습 마감 시점에 고정됩니다. 그 날짜 이후에 일어난 일은 그 안에 없고, 사적인 것은 애초에 있었던 적이 없습니다. 가중치는 공개 텍스트로 형성되었기 때문입니다. 그러니 기억은 두 가지 예측 가능한 공백을 남깁니다. 최신성(마감 시점 이후의 모든 것)과 프라이버시(공개 학습 데이터에 없었던 모든 것)입니다.

이 두 공백은 실은 두 측면에서 본 하나의 공백이고, 같은 방식으로 메워집니다. 필요한 텍스트를 요청에 넣는 것입니다. 최근 기사나 관련 데이터베이스 행을 직접 가져와서 질문과 함께 제공하면, 모델은 그것을 회상하는 대신 읽습니다. 이것이 RAG, 즉 검색 증강 생성(retrieval-augmented generation)의 핵심 아이디어이며, 답변이 최신이거나 고유한 정보를 반영해야 할 때마다 사용하게 됩니다.

파라메트릭 지식에는 출처가 딸려 있지도 않습니다. 사실들은 출처 표기가 있는 별개의 항목으로 저장된 것이 아니라 가중치 전체에 뒤섞여 있으므로, 모델은 무언가가 어디서 왔는지 믿을 만하게 말해줄 수 없습니다. 모델이 만들어내는 인용은 기록에서 검색된 것이 아니라 맞아 보이도록 생성된 것이며, 이것이 바로 당신이 제공하는 텍스트에 답을 근거 짓는 것이 그럴 만한 가치가 있는 이유입니다.

만들 때의 실무적인 규칙: 최신이거나 고유한 사실을 기억에서 회상하도록 모델에 요청하지 마십시오. 사실이 반드시 맞아야 한다면, 가져와서 제공하고, 모델은 그것에 대해 추론하도록 하십시오.

Juno모델이 모르는 것 파라메트릭 지식, 즉 가중치에 인코딩된 사실은 학습 마감 시점에 고정되고 출처가 없으므로, 모델은 최근 정보와 사적인 정보를 놓치고 어떤 것도 믿을 만하게 인용할 수 없습니다. 두 공백 모두 같은 방식으로 메워집니다. 관련 텍스트를 가져와 요청에 넣는 것인데, 이것이 RAG의 아이디어입니다. 규칙적으로, 최신이거나 고유한 사실을 모델이 기억에서 회상하게 하지 말고, 직접 제공한 뒤 그것에 대해 추론하게 하십시오.

모델의 파라메트릭 메모리, 즉 가중치(학습 중에 고정된 조율 가능한 숫자)에 담긴 지식은 버전 관리도 검증도 되지 않습니다. 그것을 비교할 수도 없고, 어떤 주장이 어디에 있는지 짚을 수도 없고, 그것이 변질되지 않았음을 확인할 수도 없습니다. 그러니 정확하거나 최신이거나 규정을 준수해야 하는 무언가에는 모델의 말을 믿지 말고, 답을 검색된 소스에 근거 짓고 그 소스를 인용하십시오. 내재된 지식을 신뢰할 만한 유창한 사전(prior)으로 다루되, 공식 기록으로 다루지는 마십시오.

마감 시점 역시 단일 날짜가 암시하는 것보다 훨씬 모호합니다. 학습 데이터는 그 날짜까지 균일하게 채워져 있지 않습니다. 최근 자료는 오래되고 잘 다뤄진 자료보다 더 얇고 덜 소화되어 있어서, 최근이지만 마감 시점 이전인 사실들은 불균등하게 알려져 있습니다. 모델은 마감 시점보다 10년 전의 사실은 정확히 맞히면서도 1년 전의 것에 대해서는 자신감 있게 틀릴 수 있습니다. 그래서 "마감 시점 이전인가?"라는 질문은 보이는 것보다 더 약한 보장이며, 반드시 맞아야 하는 것은 무엇이든 검색해야 하는 이유입니다.

공백에 맞는 도구를 골라야 합니다. 흔히 쓰이는 두 가지는 서로 교체 가능한 것이 아니기 때문입니다. 검색은 요청 시점에 사실을 제공하고 소스가 갱신되면 최신 상태를 유지합니다. 파인튜닝, 즉 당신의 예제로 학습을 이어가는 것은 형태와 행동, 어조, 형식, 모델이 당신의 규칙을 따르는 방식을 바꾸지만, 지식을 주입하는 방법으로는 형편없고 낡은 방식입니다. 학습으로 넣은 사실은 다시 고정되어 갱신하기 어렵고 여전히 출처가 없습니다. 진실을 위해서는 검색을, 어떻게 들려야 하는지를 위해서는 파인튜닝을 쓰십시오.

Juno모델이 모르는 것 파라메트릭 메모리는 버전 관리도 검증도 되지 않으므로, 정확하거나 최신이거나 규정을 준수해야 하는 것에는 모델의 말이 아니라 검색된 소스에 답을 근거 짓고 그것을 인용하십시오. 마감 시점이 모호하다는 것을 기억하십시오. 최근이지만 마감 이전인 사실은 불균등하게 알려져 있으니, 반드시 맞아야 하는 것은 검색하십시오. 그리고 공백에 맞는 도구를 쓰십시오. 검색은 사실을 제공하고, 파인튜닝은 형태와 행동을 바꿀 뿐 지식을 주입하는 낡은 방식입니다.

모델이 사실을 지어내는 이유

때로 모델은 완전한 자신감으로 거짓을 말합니다. 아무도 한 적 없는 인용문, 존재하지 않는 함수, 결코 쓰인 적 없는 연구에 대한 인용 같은 것들입니다. 이를 **환각(hallucination)**이라고 부르는데, 앞의 몇 절을 지나온 지금은 이것이 신비롭기보다는 거의 필연적으로 느껴질 것입니다.

그 이유는 모델에게 "참"과 "거짓"에 대한 내적 감각이 없다는 것입니다. 모델에게는 "그럴듯한 텍스트"와 "그럴듯하지 않은 텍스트"만 있습니다. 대부분의 경우 이 둘은 일치합니다. 모델이 학습한 텍스트에서는 참인 진술이 거짓인 진술보다 훨씬 많기 때문에, 그럴듯한 연속은 대개 맞는 것이기도 합니다.

하지만 모델이 공백에 부딙힐 때, 즉 한 번도 학습한 적 없는 것이나 결코 볼 수 없었던 당신의 사적인 데이터에 부딧힐 때, 모델은 멈춰서 그 공백을 표시하지 않습니다. 자신이 무엇을 모르는지 믿을 만하게 말할 수 없습니다. 늘 하던 그 한 가지 일을 합니다. 가장 그럴듯하게 들리는 연속을 만들어내는 것입니다.

자신감 있고, 잘 짜여진, 틀린 답이야말로 종종 가장 그럴듯하게 들리는 것 그 자체입니다. 모델을 유창하게 만드는 그 똑같은 메커니즘이, 틀렸을 때도 유창하게 만듭니다.

여기서 곰곰이 생각해봐야 할 부분입니다. 환각은 프롬프트로 완전히 없앨 수 있는 결함이 아닙니다. 줄일 수는 있고, 다음 장들에서 그 방법을 보여줍니다. 모델에게 "모른다"고 말해도 된다고 알려주는 것은 조금 도움이 됩니다. 당신이 제공한 텍스트에서만 답하라고 요청하는 것은 훨씬 더 도움이 됩니다. 이제는 고정된 학습 지식에서 채워야 할 공백이 없기 때문입니다. 하지만 그 경향은 모델이 근본적으로 무엇인지, 즉 내부에 사실 검증기가 없는 확률 기계라는 데서 나오므로, 지속적인 해법은 구조적인 것입니다. 학습 데이터에 있었기를 바라기보다 필요한 사실을 직접 건네주고, 중요한 답은 신뢰하기보다 검증하는 것입니다.

이 장에서 다른 무엇보다 하나의 규칙만 가져가십시오. 모델의 자신감은 그것이 맞는지에 대해 아무것도 말해주지 않습니다. 유창함과 정확함은 같은 과정에서 만들어지고 늘 서로 갈라집니다. 어떤 단일한 답이든 틀릴 수 있다고 가정하고 만드십시오. 실제로 어떤 단일한 답이든 틀릴 수 있기 때문입니다.

Juno모델이 사실을 지어내는 이유 환각은 모델이 맞는 것처럼 들리지만 아닌 텍스트로 자신감 있게 공백을 메우는 것입니다. "그럴듯함"만 알고 "참"은 모르기 때문입니다. 발생 빈도는 낮출 수 있지만 프롬프트만으로 완전히 없앨 수는 없으니, 안전한 방법은 모델에게 필요한 사실을 건네주고 중요한 답은 확인하는 것입니다. 그리고 가장 중요한 것을 기억하십시오. 자신감 있는 어조는 증거가 아니니, 확신 그 자체를 결코 신뢰하지 마십시오.

모델이 인용문이나 함수, 출처를 지어내고 완전한 자신감으로 진술할 때, 그것은 환각이며, 모델이 작동하는 방식에서 곧바로 나오는 결과입니다. 모델에게는 진실에 대한 신호가 없습니다. 연속을 "그럴듯한 텍스트"와 "그럴듯하지 않은 텍스트"로 점수화하고, 대부분의 경우 그럴듯함과 진실이 겹치는 것은 진실한 진술이 학습 데이터를 지배했기 때문입니다.

환각은 정확히 그 겹침이 깨지는 곳에서 급증합니다. 지식의 경계, 드물거나 소수 주제, 그리고 애초에 학습한 적 없는 당신의 사적인 데이터에서요. 그곳에서도 모델은 여전히 자신의 가장 그럴듯한 연속을 만들어내며, 유창하고 잘 짜여진 틀린 답이 종종 가장 높은 점수를 받습니다.

그러니 이것을 문구만으로 고칠 수는 없지만, 완화 방법들이 실제로 얼마나 도움이 되는지에 따라 순위를 매길 수는 있습니다. 단연 가장 큰 지렛대는 근거화(grounding)입니다. 실제 사실을 프롬프트에 넣고, 제공된 텍스트만 근거로 답하도록, 이상적으로는 그것을 인용하도록 모델에게 지시하십시오. 이제는 고정된 학습 지식에서 채워야 할 공백이 없습니다. 제공할 올바른 텍스트를 검색하는 것 자체가 하나의 기법이며, RAG에서 다룹니다.

그다음으로, 모델에게 명시적인 도피구를 주십시오. 추측하는 대신 "모른다"고 답할 수 있는 권한을 주는 것인데, 이는 지어내야 한다는 압박을 어느 정도 없애줍니다. 그리고 사실 관련 작업에는 temperature(토큰 선택의 무작위성 다이얼)를 낮게 유지하십시오. 높은 무작위성은 그럴듯하지 않은, 근거 없는 연속으로 향하는 문을 넓히기 때문입니다.

실무적인 결론은 이렇습니다. 모델을 당신이 제공한 컨텍스트로 제한하고, 중요한 출력은 검증하십시오. 프롬프트 문구는 이런 구조적인 조치 위에 얹는 작은 조정으로 다루고, 그것만으로 전체 방어선을 세우려 하지는 마십시오. 자신감 있게 들리는 모델은 그것이 맞는지에 대해 아무것도 말해준 것이 없습니다. 유창함과 정확함은 같은 과정에서 나오고 늘 갈라지기 때문입니다.

Juno모델이 사실을 지어내는 이유 환각은 모델이 진실 검증 없이 유창하고 그럴듯한 텍스트를 내놓는 것이며, 지식의 경계와 본 적 없는 데이터에서 더 심해집니다. 효과에 따라 해법의 순위를 매기십시오. 당신이 제공한 텍스트에 근거 지우기(가장 큰 지렛대), "모른다"고 말할 여지 주기, 제공된 컨텍스트에서만 답하고 그것을 인용하도록 지시하기, 사실 관련 작업에는 temperature를 낮게 유지하기입니다. 컨텍스트로 제한하고 중요한 출력은 검증하십시오. 문구 트릭만으로 버티지 마십시오.

환각은 모델이 자신감 있고 잘 짜여진, 그러나 거짓인 텍스트를 내놓는 것이며, 이는 고칠 수 있는 버그가 아니라 아키텍처의 속성입니다. 모델은 진실 신호 없이 확률로 연속의 순위를 매기므로, 학습의 경계, 드문 주제, 최신 사건, 사적인 무언가의 경계에서도 여전히 가장 그럴듯한 추측을 내놓습니다. 이것을 없앨 수는 없습니다. 당신의 일은 틀린 답이 중요한 무언가에 도달하기 전에 잡히거나 봉쇄되도록 시스템을 설계하는 것입니다.

이는 세 가지 구조적인 조치를 의미하며, 그중 어느 것도 프롬프트 문구가 아닙니다. 모든 답의 형태를 검증하십시오. 모델이 스키마에 맞춰 데이터를 반환하게 만들면, 잘못 형성되거나 범위를 벗어난 출력이 조용히 하류로 흘러가지 않고 눈에 띄게 실패합니다. 이것이 구조화된 출력이 당신에게 주는 것입니다. 실제 환각률을 평가(evals)로, 즉 정답이 알려진 입력들로 이루어진 별도의 held-out 세트로 자동으로 점수화해서 측정하십시오. 몇 개의 프롬프트를 눈으로 훑어보고 괜찮다고 부르는 것이 아니라요. 그리고 틀리는 비용이 큰 곳이라면 어디든 사람을 루프 안에 두십시오. 어떤 자동화된 방어막도 모든 것을 잡아내지는 못하기 때문입니다.

이 모든 것 아래에 있는 함정은 캘리브레이션입니다. 모델의 자신감은 잘 캘리브레이션되어 있지 않습니다. 진술된 확신은 정확성의 증거가 아니고, 답의 어조는 그 진실 값에 대한 정보를 전혀 담고 있지 않습니다. 그러니 모델이 자신감 있게 들린다는 것에 의사결정을 걸지 마십시오.

포기 경로(abstain path)를 만들고 선호하십시오. "주어진 것으로는 이를 답할 수 없습니다"라는 승인된 답변을 만들고, 포기를 저렴하게 만들어서 시스템이 지어내는 대신 그것을 택하도록 하십시오. 어떤 단일한 답도 틀릴 수 있다고 가정하고 피해를 봉쇄한다는 이 구조적인 사고방식이 안전과 한계 장 전체를 이루는 토대입니다.

Juno모델이 사실을 지어내는 이유 환각을 삭제할 수는 없으니 봉쇄를 위해 엔지니어링하십시오. 스키마로 구조를 검증하고, 몇 개의 출력을 눈으로 훑는 대신 평가로 비율을 측정하고, 틀리는 것이 비싼 곳에는 사람을 두십시오. 모델의 자신감은 심하게 캘리브레이션이 안 되어 있으니, 확신에 찬 어조는 증거가 아니며 그것에 의사결정을 걸어서는 안 됩니다. 시스템에 저렴하고 승인된 포기 방법을 주고, 자신감 있는 추측보다 매번 그것을 선호하게 하십시오.

종합하기

이 장의 모든 내용은 하나의 아이디어를 여러 각도에서 본 것입니다. 모델은 다음 토큰을 예측하는, 고정된 숫자 더미이며, 당신이 다루게 될 거의 모든 행동이 여기서 나옵니다.

  • 한 번에 하나의 토큰을 예측합니다. 확률 순위에서요. 그래서 회상하기보다 즉흥적으로 만들어내고, 약간의 무작위성(temperature)이 답을 다양하게 만듭니다.
  • 토큰은 모델이 작업하는 조각입니다. 당신의 요금 단위이자 크기 제한 단위이며, 모델이 글자를 믿을 만하게 세지 못하는 이유입니다.
  • 컨텍스트 윈도우는 하나의 요청이 쓰는 책상이며, 모든 것이 그것을 함께 씁니다. 모델은 요청 사이에 아무것도 기억하지 못하므로, 연속성은 당신의 일입니다.
  • 모델의 지식은 학습 마감 시점에 고정되어 있고 당신의 사적인 데이터를 포함한 적이 결코 없습니다. 그래서 사실을 가져와 프롬프트에 넣는 것입니다.
  • 환각은 진실에 대한 감각 없이 그럴듯한 텍스트를 예측하는 시스템의 대가이며, 근거화와 검증으로 줄어들 뿐, 그저 문구를 다듬는 것만으로는 줄어들지 않습니다.

이것들을 함께 붙잡으면, AI 엔지니어링의 놀랄 만큼 많은 부분이 실은 다른 옷을 입은 하나의 작업이라는 것을 알게 됩니다. 올바른 텍스트를 올바른 형태로, 올바른 순간에 책상 위에 올려놓고, 출력을 무조건 믿지 않는 것입니다. 다음 장 오픈 모델과 클로즈드 모델에서는 어떤 종류의 모델들이 있고 어떻게 하나를 구할 수 있는지 살펴보고, 그다음 프롬프팅에서 이 모든 예측을 당신이 실제로 원하는 방향으로 조종하기 시작합니다.

Juno종합하기 모델에 관한 거의 모든 것은 하나의 사실에서 나옵니다. 모델은 다음 토큰을 예측하는 고정된 숫자 집합입니다. 여기서 다양한 답, 토큰 기반 비용과 한계, 요청 사이 기억 없음, 학습 마감 시점, 환각이 따라옵니다. 이 모델들로 무언가를 만드는 일의 많은 부분은 올바른 텍스트를 책상 위에 올려놓고 출력을 무조건 믿지 않는 것입니다.

하나의 사실이 이 장 전체를 만들어냅니다. 모델은 확률 분포에서 다음 토큰을 예측하는 고정된 함수입니다. 그 결과를 따라가면 당신만의 작업용 모델을 갖게 됩니다.

  • 예측에 샘플링이 더해지면 출력이 달라집니다. temperature가 그 정도를 정합니다.
  • 토큰은 비용의 단위이자 한계의 단위이며, 글자 단위 작업이 코드에 속하는 이유입니다.
  • 컨텍스트 윈도우는 유한하고 상태를 갖지 않으므로, 모델이 필요로 하는 모든 것을 다시 보내고 골라내야 합니다.
  • 지식은 마감 시점에 고정되고 출처가 없으므로, 최신이거나 사적인 사실은 회상이 아니라 검색에서 나옵니다.
  • 환각은 구조적인 것이며, 근거화와 검증으로 줄어들 뿐 문구로 줄어들지 않습니다.

이 모든 것 아래의 패턴은 같습니다. 올바른 텍스트를 올바른 형태로, 올바른 시점에 컨텍스트에 넣고, 돌아오는 것을 검증하는 것입니다. 이것이 이 핸드북의 나머지가 만들어가는 것이며, 오픈 모델과 클로즈드 모델의 지형부터 시작해서 프롬프팅으로 이어집니다.

Juno종합하기 이 장 전체는 하나의 사실과 그 결과입니다. 고정된 다음 토큰 예측기가 다양한 출력, 토큰 기반 비용과 한계, 상태 없음, 지식 마감 시점, 환각을 낳습니다. 이어지는 작업은 늘 같습니다. 올바른 텍스트를 올바른 형태로 컨텍스트에 넣고, 돌아오는 것을 검증하는 것입니다. 프롬프팅이 바로 그것을 실행하기 시작하는 곳입니다.

여기서 모든 것은 한 문장으로 귀결됩니다. 고정된 함수가 당신이 제공한 텍스트에 대해 다음 토큰을 예측하며, 상태도, 진실 신호도, 계획도 없습니다. 당신이 관리하는 프로덕션 속성 하나하나가 이것의 필연적 결과입니다.

  • 출력은 확률적이므로, 단일 실행이 아니라 분포를 테스트합니다.
  • 비용과 지연 시간은 토큰, 특히 출력 토큰을 따라가므로, 예산을 잡고 스트리밍합니다.
  • 컨텍스트는 유한하고 상태를 갖지 않으므로, 기억은 실패 방식이 있는 엔지니어링 선택입니다.
  • 파라메트릭 지식은 고정되어 있고, 손실이 있고, 출처가 없으므로, 중요한 것은 무엇이든 근거화하고 인용합니다.
  • 자신감은 캘리브레이션되어 있지 않으므로, 신뢰하기보다 검증합니다.

여기서 얻는 보상은 모델이 바뀌어도 이 중 어느 것도 움직이지 않는다는 것입니다. 버전을 고정하고, 실패 방식 주위에 방어 장치를 만들어두면, 다음 릴리스는 당신이 흡수해야 할 놀라움이 아니라 당신이 평가하는 업그레이드가 됩니다. 이 핸드북의 나머지가 바로 그 방어 장치이며, 지형인 오픈 모델과 클로즈드 모델에서 시작해 프롬프팅으로 이어집니다.

Juno종합하기 한 문장이 이 장 전체를 이끕니다. 진실 신호가 없는, 고정되고 상태를 갖지 않는 다음 토큰 예측기입니다. 확률적 출력, 토큰 기반 비용, 유한한 컨텍스트, 고정된 지식, 캘리브레이션되지 않은 자신감은 모두 그 필연적 결과이며, 당신의 방어 장치는 이 각각을 봉쇄하기 위해 존재합니다. 좋은 점은 모델이 바뀌어도 이것들은 움직이지 않는다는 것이니, 버전을 고정하고 실패 방식 주위에 방어 장치를 세우십시오.