큰 그림: 다음 토큰 하나를 고르는 반복.
한 줄 결론
LLM은 답을 통째로 만들지 않고, 다음에 올 토큰 하나를 고르는 일을 답이 끝났다는 신호(끝 신호)가 나올 때까지 반복한다.
토큰은 글자 몇 개 정도 되는 텍스트 조각이다.
쉽게 보기
앱 서버가 시스템 지침 짧게 답하세요.와 질문 토마토는 과일이야?를 LLM API로 보내면 네, 과일입니다.가 돌아온다. 시스템 지침은 흔히 시스템 프롬프트라고 부르는 system 필드다. 요청을 보내고 답이 돌아오기까지 일어나는 일은 다섯 단계로 나눌 수 있다.
[앱 서버] 시스템 지침 "짧게 답하세요." + 질문 "토마토는 과일이야?"
|
| 1. 보내기
v
[LLM 서버]
2. 숫자로 바꾸기: 텍스트를 토큰으로 자르고 토큰마다 번호를 붙인다
3. 문맥 섞기: 토큰마다 앞에 나온 토큰들을 참고한다
4. 한 개 고르기: 다음에 올 토큰 하나를 고른다 -> "네"
5. 반복: "네"를 입력 끝에 붙이고 3, 4를 다시 한다
|
v
"네" -> "," -> "과일입니다." -> (끝 신호)
마지막 줄의 토큰 경계는 설명을 위한 가상 값이다. 실제로 어디서 잘리는지는 모델마다 다르다.
1. 보내기 (2편에서 자세히). 앱 서버는 시스템 지침과 질문을 한 요청에 묶어 보낸다. 모델은 이 요청에 담긴 글만 보고 답한다.
2. 숫자로 바꾸기 (2편에서 자세히). 모델은 글자를 직접 다루지 못하고 숫자만 계산한다. 그래서 서버는 텍스트를 토큰으로 자르고 토큰마다 정해진 번호로 바꾼다. 모델은 번호마다 그 토큰을 나타내는 숫자 묶음을 꺼내 계산을 시작한다.
3. 문맥 섞기 (3편에서 자세히). 같은 “과일”이라도 앞에 무엇이 왔는지에 따라 뜻이 달라진다. 모델은 토큰마다 앞에 나온 토큰들을 참고해서 그 토큰의 숫자 묶음을 고친다. 이 과정을 거치면 맨 끝 토큰의 숫자 묶음에 “짧게”, “토마토”, “과일이야?”의 정보가 함께 담긴다. 4번 단계는 이 숫자 묶음을 보고 다음 토큰을 정한다.
4. 한 개 고르기 (4편에서 자세히). 모델은 다음에 올 수 있는 토큰 후보 전체에 점수를 매긴다. “네”가 가장 높다. “아니”나 “토마토는”도 점수를 받지만 “네”보다 낮다. 이 점수를 보고 토큰 하나를 고른다.
5. 반복 (5편에서 자세히). 고른 “네”를 입력 끝에 붙이면 입력이 한 토큰 길어진다. 늘어난 입력으로 3과 4를 한 번 더 해서 쉼표를 고르고, 쉼표도 붙여서 “과일입니다.”를 고른다. “이제 끝”을 뜻하는 토큰이 골라지면 반복이 멈춘다. 이것이 끝 신호다. 매번 처음부터 다시 계산하면 느려서, vLLM(공개 모델용 오픈소스 엔진) 같은 대부분의 서빙 엔진은 앞에서 한 계산을 저장해 두고 다시 가져다 쓴다.
이 구조를 알면 평소에 보던 현상 몇 가지가 설명된다.
- 답이 길수록 늦게 끝난다. 출력 토큰 하나를 만들 때마다 3과 4를 한 번씩 거치기 때문이다.
- 출력 토큰 수가 요청의
max_tokens에 도달하면 끝 신호가 나오기 전에 반복이 멈춘다. 답이 문장 중간에서 끊기는 이유다. - 답의 앞부분은 뒷부분이 정해지기 전에 이미 확정된다. 스트리밍을 켜면 글자가 앞에서부터 차례로 나타나는 이유다.
새 용어
- 토큰: 모델이 텍스트를 다루는 단위. 한 단어일 수도 있고, 단어의 일부나 문장부호 하나일 수도 있다. API 요금과
max_tokens가 세는 것도 이 토큰이다. 끝 신호도 화면에 보이지 않는 특별한 토큰 하나다. - 스트리밍: 생성이 끝날 때까지 기다리지 않고, 고른 토큰을 바로 클라이언트에 보내는 방식. Claude API는 요청에
"stream": true를 넣으면 SSE로 조각을 보낸다. 스트리밍을 끄면 서버가 생성이 끝난 뒤 한 번에 보낸다.
더 깊이
스트리밍으로 토큰이 앞에서부터 차례로 나오는 것은 연출이 아니라 실제 생성 순서다.
다음 토큰을 고르려면 그 앞 토큰이 입력에 들어 있어야 한다. “과일입니다.”는 “네”와 쉼표가 정해진 뒤에야 고를 수 있다. 그래서 모델은 뒷부분을 먼저 만들 수 없다. 서버도 완성된 답을 미리 만들어 두었다가 나눠 보내지 않는다. 스트리밍은 이미 고른 토큰을 나머지를 기다리지 않고 바로 보낼 뿐이다.
이 순서 때문에 개발할 때 알아 둘 점이 두 가지 있다.
stream을 꺼도 답 전체를 만드는 시간은 거의 같다. 달라지는 것은 첫 글자가 화면에 나오는 시점이다. 스트리밍을 끄면 사용자는 마지막 토큰이 나올 때까지 빈 화면을 본다.- 이미 고른 토큰은 되돌리지 않는다. 모델이 앞에서 틀린 말을 하면 뒤에 바로잡는 문장을 덧붙일 수는 있어도, 앞에서 내보낸 토큰을 지우지는 않는다.
이벤트 하나가 꼭 토큰 하나는 아니다. Claude API의 텍스트 조각은 아래 모양의 SSE 이벤트로 온다(text 값은 가상 값).
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "네"}}
Claude 스트리밍 문서의 예시에는 "ello frien"처럼 두 단어에 걸친 조각이 이벤트 하나에 담겨 있다. 이벤트 하나에 토큰 여러 개가 들어 있을 수 있다는 뜻이다. 화면에 몇 글자씩 한꺼번에 나타나는 것은 이 때문이고, 생성 순서는 그래도 앞에서 뒤로 한 토큰씩이다.
출처
원글 LLM에게 질문하면 일어나는 일들을 읽고 다시 정리했다.