토크나이제이션

토크나이제이션 (Tokenization) — 경계(Boundaries)와 토큰 힐링(Token Healing)

토크나이제이션의 주요 문제인 경계 문제(boundary problem)와 이를 해결하는 토큰 힐링(token healing)을 살펴보는 페이지예요.

출처: 문서

본문

기초 — 복습 (Basics - Recap)

Tokenization은 문자열을 모델이 입력으로 사용하는 토큰 ID 시퀀스로 변환해요. 토큰은 문자, 서브워드(subword) 또는 심볼이 될 수 있어요. 토크나이저는 문자열을 토큰으로 쪼개고 ID로 변환(encoding)하며, 그 반대(decoding)도 해요. 그러면 모델은 시퀀스에 기반해서 다음 토큰을 예측해요. 예를 들어 "Hello, how are"라는 문장은 Hello, -> 432, how -> 523, are -> 87처럼 토크나이즈될 수 있어요. 모델은 다음 토큰(예: 75, you로 디코딩)을 예측해서 일관된 텍스트를 생성할 수 있어요. LLM을 챗봇으로 사용하려면 템플릿을 따르는 대화로 파인튜닝하면 돼요. 모델은 Beginning of String(BOS) 토큰(<s>)으로 시작하고, End of String(EOS) 토큰(</s>)으로 생성을 멈춰요. 혼란을 피하고 적절한 정지를 보장하려면 형식화에 희귀 문자열을 쓰고 각 Assistant 응답 뒤에 EOS 토큰을 삽입할 수 있어요. 예를 들면:

<s>[INST] Hello, how are you? [/INST] Fine, and you?</s>[INST] I'm doing great! [/INST] Glad to hear!</s>

경계 문제 (The Boundary Problem)

이 문제를 설명하기 위해 다른 예시를 들어 볼게요. https://mistral.ai 같은 URL을 생각해 보세요.

이런 URL은 잠재적으로 https + :// + mistral + .ai처럼 토크나이즈될 수 있어요.

https는 아주 빈번한 문자열이므로 그 자체로 토큰일 가능성이 매우 높고, 대부분의 안전한 유효한 URL은 그걸로 토크나이즈될 거예요.

그럼 http 다음 토큰을 생성하라고 모델에 요청하면 어떻게 될까요? https가 아니라 http를 쓰고 있다는 점을 주목하세요.

https를 만들기 위해 s를 예측하려 할까요? 아주 가능성이 낮아요. 모델은 http + s 같은 토큰 시퀀스를 본 적이 없어요. 대신 학습 중에 항상 https를 단일 토큰으로 봤어요. 그래서 편향되어 ://를 바로 예측할 가능성이 더 높아져, 안전하지 않은 URL이 돼요.

이 경우엔 경계 문제가 살짝 편향만 도입할 뿐이에요. 하지만 어떤 경우에는 완전히 잘못된 출력으로 이어질 수 있어요.

https:를 주고 다음 토큰을 예측하라고 해볼게요.

토크나이저는 이를 https + :으로 인코딩해요.

그럼 다음 토큰은 뭘까요? //? 짐작했겠지만 그렇지 않아요. 학습 데이터에서 그런 시퀀스를 거의 또는 전혀 본 적이 없거든요. 추가된 :이 분포를 엄청나게 흔들 수 있어요. 모델은 정확한 토큰을 사용하는 것조차 피하면서, 학습 데이터에서 실제로 본 토큰 시퀀스를 우선시하는 상황에 처할 수 있어요. 이는 경우에 따라 완전히 잘못된 출력(여기선 유효하지 않은 URL)으로 이어질 수 있어요.

앞선 템플릿을 생각해 보면, 예를 들어 ...[/INST]...이 어떻게 토크나이즈될지 알 방법이 없어요. 아주 나쁜 시나리오에서는 [/INST] Fine이 [/ + INST + ] Fine으로 토크나이즈될 수도 있어요. 그래서 우리 특수 문자열 주변에 경계 문제를 만들어내요.

이것은 토크나이제이션에서 경계 문제를 이해하고 해결하는 것의 중요성을 강조해요. 모델이 다양하고 포괄적인 데이터셋으로 학습되도록 보장하면 이 문제 중 일부를 완화할 수 있고, 다른 기법들도 모델을 더 견고하게 만들 수 있어요.

해결책 A (Solution A)

가능한 해결책 중 하나는 템플릿을 재작성해서 마지막 토큰이 깔끔한 기대 토큰이 되고 기대 시퀀스가 되도록 보장하는 거예요. 특수 문자열로 감싼 사용자 메시지를 따로 인코딩하고, 그다음에만 assistant 응답을 인코딩해서 두 시퀀스를 연결하면 돼요.

결과는 이렇게 돼요:

BOS_ID
+ encode("[INST] Hello, how are you? [/INST]")
+ encode("Fine, and you?") + EOS_ID
+ encode("[INST] I'm doing great! [/INST]")
+ encode("Glad to hear!") + EOS_ID

이 방법을 쓰면 대부분의 경우 깔끔한 [/INST]로 제대로 토크나이즈되며 끝나는, 모델에 완성을 위해 제공되는 시퀀스가 경계 문제를 겪지 않는다는 걸 확신할 수 있어요.

토크나이저 V1 (Tokenizer V1)

이 모든 것을 고려하면, 우리는 사실 우리의 첫 모델인 Mistral 7B v0.1과 Mistral 7B v0.2, 그리고 Mixtral 8x7B v0.1에 쓰였던 최초의 토크나이저에 대해 알아야 할 모든 것을 이해할 아주 가까운 지점에 있어요. 유일한 차이는 문자열 표현에 관한 거예요. 앞서 문자열 표현이 이렇게 생길 수 있다고 언급했죠:

<s>[INST] Hello, how are you? [/INST] Fine, and you?</s>[INST] I'm doing great! [/INST] Glad to hear!</s>

하지만 우리 최초 토크나이저들은 sentencepiece를 사용했는데, 이는 기본적으로 앞에 공백(whitespace)을 붙여서 인코딩해요. 즉 각 인코딩 단계에서 시작 부분에 새 공백을 추가한다는 뜻이에요. 예를 들어 encode("[INST] Hello, how are you? [/INST]")는 실제로 " [INST] Hello, how are you? [/INST]"를 인코딩한다는 의미예요.

이를 고려하면 결과는 이렇게 돼요:

<s> [INST] Hello, how are you? [/INST] Fine, and you?</s> [INST] I'm doing great! [/INST] Glad to hear!</s>

해결책 B — 토큰 힐링 (Solution B - Token Healing)

앞의 해결책은 더 일반적인 경우의 문제를 완전히 해결하지는 못하고, 특수 문자열 주변만 해결해요. 즉 assistant 응답용 문자열을 접두어(prefix/prefill)로 넣고 싶다면 다시 경계 문제를 만나게 된다는 뜻이에요.

이 문제는 채팅 완성용으로 파인튜닝되지 않은 더 기본적인 모델에도 영향을 줘요. 즉 베이스 모델을 사용하면 경계 문제의 피해자가 되기 쉽다는 뜻이에요.

Token Healing은 시퀀스의 마지막 토큰을 제거하고, 다음 토큰 생성이 제거된 토큰에 해당하는 문자열로 시작하도록 제약함으로써 문제를 해결해요. 이렇게 하면 토큰 생성 하나의 대가로 경계 문제를 완전히 해결해요.

URL 예시를 들어 볼게요. https + : 같은 시퀀스가 있을 때 마지막 토큰 :을 제거하고, 다음 토큰 생성이 ":"로 시작하도록 제약해서 모델이 ://을 제대로 예측할 수 있게 해요.

더 알아보기 (Learn more)