토크나이제이션
토크나이제이션 (Tokenization) — 기초 (Basics)
LLM이 텍스트를 토큰으로 쪼개고, 토큰 ID로 인코딩·디코딩하며, 어떻게 모델과 대화할 수 있는지의 기초를 살펴보는 페이지예요.
출처: 문서
본문
LLM — 복습 (Recap)
오늘날 우리가 아는 LLM(Large Language Models)은 대부분 transformer 아키텍처에 기반한 자기회귀(auto-regressive) 모델이에요. 애플리케이션은 다양하고 뛰어난 챗봇으로 세상을 놀라게 하지만, 그 밑바탕 기술은 단순한 원리를 따르고 있어요: LLM은 Text Completion Machine(텍스트 완성 기계)로 설명할 수 있어요. 문장, 텍스트 또는 문자열이 주어지면, 모델은 그것을 "Tokens"라고 불리는 여러 조각으로 쪼개요. 이 토큰들의 시퀀스로 학습된 모델은 시퀀스를 완성하기 위해 다음 토큰을 예측해요.
토크나이제이션이란? (What is Tokenization?)
Tokenization은 요청(보통 문자열)을 모델이 입력으로 받을 토큰 ID 시퀀스로 변환하는 과정이에요. 토큰은 단일 문자부터 서브워드(subword)나 심볼까지 뭐든 될 수 있어요. 모델은 그에 해당하는 ID와 함께 토큰 어휘(vocabulary)를 갖고 있어요. 토크나이저의 역할은 문자열을 쪼개서 토큰 ID 시퀀스로 변환하는 것인데, 이 단계를 "encoding"이라고 해요. 그리고 토큰 ID 시퀀스를 다시 문자열로 변환하는 것도 토크나이저의 역할인데, 이 단계를 "decoding"이라고 해요. 토큰 ID 시퀀스가 주어지면 모델은 다음 토큰을 예측하려 해요.
단순함을 위해 토큰을 문자와 완전한 단어로 설명해 볼게요.
다음과 같은 문장이 있다고 상상해 보세요: "Hello, how are"
- 토크나이저는 먼저 문장을 여러 조각(토큰)으로 쪼개요. 예를 들어:
Hello,howare
- 토크나이저는 이어서 각 토큰에 해당하는 토큰 ID를 얻어요. 예를 들어:
Hello,->432how->523are->87이 토큰 ID 시퀀스가 모델에 제공돼요.
- 모델은 다음 토큰을 예측하려 해요. 이렇게 예측했다고 해볼게요:
75그러면 전체 토큰 ID 시퀀스가 돼요:432+523+87+75
- 이 시퀀스는 다시 완전한 문자열로 디코딩될 수 있어요:
Hello,+how+are+you->Hello, how are you
이 과정을 필요할 때마다 반복할 수 있어요.
- 모델은 다시 다음 토큰을 예측하려 해요. 이렇게 예측했다고 해볼게요:
868그러면 전체 토큰 ID 시퀀스가 돼요:432+523+87+75+868
- 이 시퀀스는 다시 완전한 문자열로 디코딩돼요:
Hello,+how+are+you+?->Hello, how are you?
인코딩, 예측, 디코딩의 이 반복 과정 덕분에 모델이 일관성 있고 문맥에 맞는 텍스트를 생성할 수 있어요.
나만의 토크나이저 만드는 법을 더 배우고 싶다면 여기를 보세요!
모델과 어떻게 대화할까? (How to talk with a model?)
이제 LLM과 어떻게 대화하거나 챗봇으로 사용할지 궁금할 수 있겠죠. 아이디어는 간단해요: 모델을 대화와 다이얼로그로 파인튜닝해서, 모델이 일관된 응답으로 다이얼로그를 완성하는 법을 배우게 하는 거예요.
우리가 Chat Template이라고 부를 것, 즉 모델이 파인튜닝될 다이얼로그 형식을 설계해 볼게요.
User: Hello, how are you?
Assistant: Fine, and you?
User: I'm doing great!
Assistant: Glad to hear!
이런 형식으로 학습된 모델은 다이얼로그를 완성할 수 있어서 여러분과 대화하는 듯한 착각을 줄 수 있어요.
사실, 아직 언급하지 않은 게 있어요: 토큰 시퀀스에는 항상 토큰이 하나 추가되는데, 보통 BOS(Beginning of String)라고 불러요. 이 토큰은 본질적으로 엔진을 시작하며 첫 번째 토큰 역할을 해요. 이 토큰의 표준 표현은 <s>예요. 토크나이저는 실제로 이 토큰의 문자열 표현을 보지 않아요. 대신 BOS의 토큰 ID를 나머지 토큰 시퀀스 앞에 붙여요. 앞선 예시를 생각해 보면 이렇게 될 거예요:
BOS_ID + encode("User: Hello, how are you?
Assistant: Fine, and you?
User: I'm doing great!
Assistant: Glad to hear!")
또는 문자열 표현만 사용한다면:
<s>User: Hello, how are you?
Assistant: Fine, and you?
User: I'm doing great!
Assistant: Glad to hear!
이렇게 하면 BOS로 시작하는 긴 토큰 ID 시퀀스가 생겨요.
하지만 이 Chat Template에는 문제가 있어요. 먼저, 이런 전형적인 다이얼로그 형식을 쓰면, 사용자가 이 형식과 비슷한 걸 물어볼 때마다 모델이 혼란스러워져요. 이 템플릿과 비슷한 건 아무것도 물어볼 수 없게 되는 거죠. 해결책은 대화에서 실제로 쓰일 가능성이 아주 낮은 희귀 문자열을 사용해서 채팅 템플릿의 형식을 정하는 거예요.
두 번째 문제는 모델을 멈추는 방법이에요. 현재 이렇게 모델을 학습시키면, 멈추라고 하지 않는 한 절대 텍스트 완성을 멈추지 않아요. 이 문제의 해결책은 EOS, End of String이라고 부르는 것을 사용하는 거예요. 이 토큰은 예측되면 생성이 멈춰야 한다는 걸 알려줘요. BOS와 비슷하게 </s>로 표현할 거예요. 토크나이저는 이 문자열을 절대 보지 않고, 우리가 모델을 멈추고 싶을 때마다 EOS_ID가 삽입돼요. 우리의 경우 각 Assistant 응답 뒤에요.
요약하면, 우리가 원하는 건:
- 채팅 템플릿을 형식화하고 구성하기 위해 아주 희귀한 문자열을 사용하는 것. 예를 들어 사용자 지시를
[INST]와[/INST]로 감쌀 수 있어요. - 각 Assistant 응답 끝에 EOS를 삽입해서 모델에게 언제 멈춰야 하는지 알려주는 것.
이 아이디어로 다음과 같은 템플릿을 만들 수 있어요:
BOS_ID
+ encode("[INST] Hello, how are you? [/INST] Fine, and you?") + EOS_ID
+ encode("[INST] I'm doing great! [/INST] Glad to hear!") + EOS_ID
또는 문자열 표현만 사용한다면:
<s>[INST] Hello, how are you? [/INST] Fine, and you?</s>[INST] I'm doing great! [/INST] Glad to hear!</s>
이게 아직 우리가 mistral-common으로 구현한 것과는 거리가 멀지만, 많은 사람이 여기서 멈춰도 돼요. 이게 이해해야 할 최소한의 요구 사항이기 때문이에요. 다만 토크나이저와 LLM 안에는 이해하는 게 중요한 숨은 문제가 몇 가지 있어요. 특히 **경계 문제(boundary problems)**예요.
더 알아보기 (Learn more)
- 원문 (GitHub)
- 경계 문제와 토큰 힐링: Boundaries
- 나만의 토크나이저 만들기: Tokenizer