🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

BPE 토크나이저 완전 분석 - 슬라이드 1/7
A

AI Generated

2025. 11. 28. · 61 Views

BPE 토크나이저 완전 분석

GPT-4와 같은 대규모 언어 모델이 텍스트를 이해하는 첫 번째 단계인 BPE 토크나이저를 분석합니다. Rust로 구현된 고성능 토크나이저의 내부 구조부터 Python 래퍼, 학습 방법까지 완벽하게 살펴봅니다.


목차

  1. BPE(Byte Pair Encoding)란?
  2. rustbpe 폴더 구조 분석
  3. lib.rs 소스 코드 읽기
  4. tokenizer.py 래퍼 분석
  5. tok_train.py로 토크나이저 학습
  6. GPT-4 스타일 토크나이저의 비밀

1. BPE(Byte Pair Encoding)란?

어느 날 김개발 씨가 GPT API를 사용하다가 문득 궁금해졌습니다. "이 모델은 내가 보낸 문장을 도대체 어떻게 이해하는 걸까?" 토큰 수에 따라 과금된다는 것은 알겠는데, 토큰이 정확히 무엇인지 알 수 없었습니다.

**BPE(Byte Pair Encoding)**는 텍스트를 작은 단위로 쪼개는 토크나이저 알고리즘입니다. 마치 레고 블록처럼 자주 등장하는 문자 조합을 하나의 토큰으로 합쳐나가는 방식입니다.

이 알고리즘을 이해하면 LLM이 텍스트를 처리하는 근본 원리를 파악할 수 있습니다.

다음 코드를 살펴봅시다.

# BPE의 핵심 아이디어: 빈도 기반 병합
text = "low lower lowest"

# 1단계: 문자 단위로 분리
tokens = list(text)  # ['l', 'o', 'w', ' ', 'l', 'o', 'w', 'e', 'r', ...]

# 2단계: 가장 빈번한 쌍 찾기
# ('l', 'o')가 3번 등장 -> 'lo'로 병합

# 3단계: 병합 후 새로운 토큰 생성
# ['lo', 'w', ' ', 'lo', 'w', 'e', 'r', ...]

# 4단계: 반복하여 어휘 사전 구축
vocab = {'lo': 256, 'low': 257, 'er': 258}  # 병합 규칙 저장

김개발 씨는 입사 6개월 차 주니어 개발자입니다. 요즘 회사에서 LLM을 활용한 프로젝트를 진행하고 있는데, API 비용이 예상보다 많이 나와서 고민입니다.

선배 개발자 박시니어 씨가 다가와 물었습니다. "토큰이 뭔지 알아요?" 김개발 씨는 대충 단어 같은 거라고 대답했습니다.

박시니어 씨가 고개를 저었습니다. "토큰을 제대로 이해하려면 BPE부터 알아야 해요." 그렇다면 **BPE(Byte Pair Encoding)**란 정확히 무엇일까요?

쉽게 비유하자면, BPE는 마치 속기사가 자주 쓰는 단어를 기호로 줄여 쓰는 것과 같습니다. "그리고"를 매번 쓰는 대신 특수 기호 하나로 표시하면 빠르게 기록할 수 있겠죠.

BPE도 마찬가지로 자주 등장하는 문자 조합을 하나의 토큰으로 압축합니다. BPE가 없던 시절에는 어땠을까요?

초기의 자연어 처리에서는 단어 단위로 토큰을 나눴습니다. 하지만 이 방식에는 치명적인 문제가 있었습니다.

바로 OOV(Out of Vocabulary) 문제입니다. 학습 데이터에 없던 새로운 단어가 등장하면 모델이 전혀 처리할 수 없었습니다.

예를 들어 "ChatGPT"라는 단어를 학습하지 않았다면, 이 단어는 그저 알 수 없는 기호에 불과했습니다. 새로운 신조어나 전문 용어가 등장할 때마다 어휘 사전을 다시 만들어야 했습니다.

바로 이런 문제를 해결하기 위해 BPE가 등장했습니다. BPE의 핵심 아이디어는 단순합니다.

텍스트를 바이트 수준까지 분해한 뒤, 가장 자주 등장하는 쌍을 찾아 병합하는 것입니다. 이 과정을 원하는 어휘 크기에 도달할 때까지 반복합니다.

위의 코드를 살펴보겠습니다. 먼저 텍스트를 문자 단위로 분리합니다.

그다음 가장 빈번하게 등장하는 문자 쌍을 찾습니다. "low", "lower", "lowest"에서 'l'과 'o'가 함께 등장하는 빈도가 높으니 이를 'lo'라는 새 토큰으로 병합합니다.

이 과정을 반복하면 'lo' 다음에는 'low'가, 그다음에는 'low'+'er' 같은 조합이 만들어집니다. 최종적으로 자주 쓰이는 서브워드들의 사전이 완성됩니다.

실제 현업에서 이것이 왜 중요할까요? GPT-4의 토크나이저는 약 10만 개의 토큰을 가지고 있습니다.

"Hello"는 하나의 토큰이지만, "안녕하세요"는 여러 토큰으로 쪼개집니다. 이것이 바로 영어보다 한국어 처리 비용이 더 높은 이유입니다.

하지만 BPE 덕분에 아무리 생소한 단어라도 처리할 수 있습니다. "메타버스"라는 단어를 모르더라도 "메", "타", "버", "스"의 조합으로 표현할 수 있기 때문입니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.

"아, 그래서 한국어가 토큰을 더 많이 소모하는군요!"

실전 팁

💡 - BPE 어휘 크기가 클수록 효율적이지만, 메모리 사용량도 증가합니다

  • tiktoken 라이브러리로 GPT 토크나이저를 직접 실험해볼 수 있습니다

2. rustbpe 폴더 구조 분석

김개발 씨는 BPE의 개념을 이해했지만, 실제로 어떻게 구현되는지 궁금해졌습니다. 박시니어 씨가 GitHub에서 rustbpe 프로젝트를 열어 보여주었습니다.

"직접 코드를 보면 더 확실히 이해될 거예요."

rustbpe는 Rust로 구현된 고성능 BPE 토크나이저입니다. Rust의 속도와 안전성을 활용하여 Python보다 수십 배 빠른 토큰화가 가능합니다.

폴더 구조를 이해하면 전체 시스템의 설계 철학을 파악할 수 있습니다.

다음 코드를 살펴봅시다.

rustbpe/
├── Cargo.toml          # Rust 프로젝트 설정 파일
├── src/
│   └── lib.rs          # 핵심 BPE 알고리즘 구현
├── tokenizer.py        # Python 래퍼 (사용자 인터페이스)
├── tok_train.py        # 토크나이저 학습 스크립트
├── models/
│   └── gpt4.model      # 학습된 어휘 사전 파일
└── tests/
    └── test_tokenizer.py  # 테스트 코드

# Rust(lib.rs): 고속 인코딩/디코딩 엔진
# Python(tokenizer.py): 편리한 API 제공
# 이 구조가 "Rust 코어 + Python 래퍼" 패턴의 전형

김개발 씨가 처음 rustbpe 저장소를 열었을 때 당황했습니다. Rust 파일도 있고 Python 파일도 있는데, 이게 도대체 무슨 프로젝트일까요?

박시니어 씨가 설명을 시작했습니다. "이건 요즘 유행하는 하이브리드 구조예요.

성능이 중요한 핵심 로직은 Rust로 작성하고, 사용자가 편하게 쓸 수 있도록 Python으로 감싸는 거죠." 그렇다면 왜 이런 구조를 사용할까요? 마치 자동차와 같습니다.

엔진(Rust)은 고성능이어야 하지만, 운전자가 직접 엔진을 조작하진 않습니다. 핸들과 페달(Python)이라는 인터페이스를 통해 쉽게 조작하죠.

rustbpe도 마찬가지입니다. Cargo.toml은 Rust 프로젝트의 설정 파일입니다.

JavaScript의 package.json, Python의 pyproject.toml과 같은 역할을 합니다. 여기에 프로젝트 이름, 버전, 의존성 등이 정의되어 있습니다.

src/lib.rs가 진짜 핵심입니다. BPE 알고리즘의 인코딩과 디코딩이 모두 여기서 일어납니다.

Rust로 작성되어 C언어 수준의 속도를 자랑합니다. tokenizer.py는 Python 사용자를 위한 래퍼입니다.

Rust 바이너리를 호출하여 결과를 Python 객체로 변환해줍니다. 덕분에 사용자는 Rust를 몰라도 토크나이저를 사용할 수 있습니다.

tok_train.py는 새로운 토크나이저를 학습시키는 스크립트입니다. 텍스트 데이터를 넣으면 BPE 알고리즘을 적용하여 어휘 사전을 만들어냅니다.

models/ 폴더에는 학습이 완료된 모델 파일이 저장됩니다. gpt4.model 같은 파일에는 병합 규칙과 어휘 사전이 바이너리 형태로 담겨 있습니다.

이 구조의 장점은 명확합니다. Python의 편리함과 Rust의 성능을 동시에 얻을 수 있습니다.

실제로 tiktoken(OpenAI의 공식 토크나이저)도 이와 동일한 구조를 사용합니다. 김개발 씨가 물었습니다.

"그럼 저도 Rust를 배워야 하나요?" 박시니어 씨가 웃으며 답했습니다. "아니요, Python 래퍼만 써도 충분해요.

하지만 내부 구조를 알면 문제가 생겼을 때 디버깅하기 훨씬 쉽죠."

실전 팁

💡 - PyO3 라이브러리를 사용하면 Rust 코드를 Python에서 직접 호출할 수 있습니다

  • 성능 병목이 있는 Python 코드는 Rust로 재작성하는 것을 고려해보세요

3. lib.rs 소스 코드 읽기

김개발 씨는 이제 핵심 엔진인 lib.rs를 들여다보기로 했습니다. Rust 문법이 낯설었지만, 박시니어 씨의 도움을 받아 한 줄씩 분석해 나갔습니다.

lib.rs는 BPE 토크나이저의 심장부입니다. 병합 규칙을 적용하여 텍스트를 토큰 ID로 변환하는 encode 함수와, 그 반대 과정인 decode 함수가 구현되어 있습니다.

Rust의 HashMap과 Vec을 활용한 효율적인 자료구조가 핵심입니다.

다음 코드를 살펴봅시다.

use std::collections::HashMap;

pub struct Tokenizer {
    vocab: HashMap<Vec<u8>, u32>,      // 바이트 시퀀스 -> 토큰 ID
    merges: Vec<(Vec<u8>, Vec<u8>)>,   // 병합 규칙 리스트
}

impl Tokenizer {
    // 텍스트를 토큰 ID 배열로 변환
    pub fn encode(&self, text: &str) -> Vec<u32> {
        let mut tokens: Vec<Vec<u8>> = text.bytes()
            .map(|b| vec![b])
            .collect();

        // 병합 규칙을 순서대로 적용
        for (p1, p2) in &self.merges {
            tokens = self.merge_pair(&tokens, p1, p2);
        }

        tokens.iter()
            .map(|t| *self.vocab.get(t).unwrap_or(&0))
            .collect()
    }
}

Rust 코드가 처음에는 외계어처럼 보일 수 있습니다. 하지만 걱정하지 마세요.

핵심 로직은 생각보다 단순합니다. 먼저 Tokenizer 구조체를 살펴보겠습니다.

두 개의 필드가 있습니다. vocab는 바이트 시퀀스를 토큰 ID로 매핑하는 사전입니다.

merges는 어떤 쌍을 어떤 순서로 병합할지 정의한 규칙 리스트입니다. 왜 바이트 단위로 처리할까요?

마치 모든 언어를 아우르는 공통 알파벳이 필요한 것과 같습니다. 한글, 영어, 이모지 모두 결국 바이트의 조합입니다.

바이트 수준에서 처리하면 어떤 언어든 동일한 방식으로 토큰화할 수 있습니다. encode 함수의 동작 과정을 따라가 봅시다.

첫 번째 단계에서 입력 텍스트를 바이트 단위로 분리합니다. "Hello"는 [72], [101], [108], [108], [111]이 됩니다.

각 숫자는 ASCII 코드입니다. 두 번째 단계에서 병합 규칙을 순서대로 적용합니다.

만약 첫 번째 규칙이 "([108], [108]) -> [108, 108]"이라면, 연속된 두 개의 'l'을 하나의 토큰으로 합칩니다. 이 과정을 모든 병합 규칙에 대해 반복합니다.

규칙의 순서가 중요합니다. 먼저 정의된 규칙이 먼저 적용되기 때문입니다.

마지막으로 각 토큰을 vocab에서 찾아 ID로 변환합니다. "Hello"가 최종적으로 [15496]처럼 하나의 ID가 될 수도 있고, [1544, 32]처럼 여러 ID가 될 수도 있습니다.

Rust의 **unwrap_or(&0)**는 안전장치입니다. vocab에 없는 토큰이 나오면 0을 반환합니다.

실제 프로덕션 코드에서는 이런 예외 처리가 필수입니다. 김개발 씨가 감탄했습니다.

"결국 for문으로 병합 규칙을 적용하는 게 전부네요!" 박시니어 씨가 고개를 끄덕였습니다. "맞아요.

알고리즘 자체는 단순해요. 다만 이걸 얼마나 빠르게 처리하느냐가 Rust를 쓰는 이유죠."

실전 팁

💡 - Rust의 HashMap은 Python의 dict와 유사하지만, 타입이 고정되어 더 빠릅니다

  • 병합 규칙의 순서를 바꾸면 완전히 다른 토큰화 결과가 나올 수 있습니다

4. tokenizer.py 래퍼 분석

이제 Python 개발자들이 실제로 사용하게 될 tokenizer.py를 분석할 차례입니다. 김개발 씨는 익숙한 Python 문법을 보니 한결 마음이 편해졌습니다.

tokenizer.py는 Rust로 구현된 토크나이저를 Python에서 편리하게 사용할 수 있게 해주는 래퍼입니다. 모델 파일 로딩, 인코딩/디코딩 메서드, 특수 토큰 처리 등 사용자 친화적인 인터페이스를 제공합니다.

다음 코드를 살펴봅시다.

import os
from pathlib import Path

class Tokenizer:
    def __init__(self, model_path: str):
        # 모델 파일에서 어휘 사전과 병합 규칙 로드
        self.vocab, self.merges = self._load_model(model_path)
        self.vocab_size = len(self.vocab)

        # 특수 토큰 정의
        self.special_tokens = {
            "<|endoftext|>": 100257,
            "<|fim_prefix|>": 100258,
        }

    def encode(self, text: str) -> list[int]:
        """텍스트를 토큰 ID 리스트로 변환"""
        # 특수 토큰 먼저 처리
        tokens = self._handle_special_tokens(text)
        # Rust 엔진 호출하여 BPE 적용
        return self._bpe_encode(tokens)

    def decode(self, ids: list[int]) -> str:
        """토큰 ID 리스트를 텍스트로 복원"""
        return self._bpe_decode(ids)

Python 래퍼의 역할은 명확합니다. 복잡한 내부 구현을 숨기고, 사용자에게 직관적인 인터페이스를 제공하는 것입니다.

마치 리모컨과 같습니다. TV의 복잡한 회로를 몰라도 버튼만 누르면 채널을 바꿀 수 있죠.

tokenizer.py가 바로 그 리모컨 역할을 합니다. init 메서드를 먼저 살펴보겠습니다.

model_path에서 사전 학습된 모델 파일을 로드합니다. 이 파일에는 vocab(어휘 사전)과 merges(병합 규칙)가 담겨 있습니다.

GPT-4 토크나이저의 경우 약 10만 개의 토큰이 정의되어 있습니다. special_tokens는 특별한 의미를 가진 토큰들입니다.

<|endoftext|>는 텍스트의 끝을 나타내고, <|fim_prefix|>는 코드 자동완성에서 사용됩니다. 이런 토큰들은 일반 BPE 규칙과 별도로 처리해야 합니다.

encode 메서드의 동작 순서를 따라가 봅시다. 먼저 텍스트에서 특수 토큰을 찾아 분리합니다.

"Hello<|endoftext|>World"라면 "Hello", <|endoftext|>, "World"로 나눕니다. 특수 토큰은 절대 쪼개지면 안 되기 때문입니다.

그다음 일반 텍스트 부분에 BPE 인코딩을 적용합니다. 이 과정에서 Rust 엔진이 호출됩니다.

Python에서 Rust 함수를 호출하는 것이죠. decode 메서드는 그 반대 과정입니다.

토큰 ID 리스트를 받아 원본 텍스트로 복원합니다. 단, 일부 정보 손실이 발생할 수 있습니다.

예를 들어 연속된 공백이 하나로 합쳐질 수 있습니다. 김개발 씨가 질문했습니다.

"특수 토큰은 왜 따로 처리하나요?" 박시니어 씨가 답했습니다. "<|endoftext|>가 '<', '|', 'e', 'n', 'd'...로 쪼개지면 모델이 텍스트 끝을 인식할 수 없게 돼요.

특수 토큰은 반드시 하나의 단위로 유지되어야 합니다." 이 래퍼 덕분에 사용자는 단 세 줄로 토크나이저를 사용할 수 있습니다. tokenizer = Tokenizer("gpt4.model"), tokens = tokenizer.encode("Hello"), text = tokenizer.decode(tokens).

간단하죠?

실전 팁

💡 - 특수 토큰의 ID는 일반 토큰 ID 범위 밖에 있어야 충돌을 피할 수 있습니다

  • decode 후 encode하면 원본과 다른 결과가 나올 수 있으니 주의하세요

5. tok train.py로 토크나이저 학습

김개발 씨는 이제 궁극적인 질문을 던졌습니다. "그럼 이 어휘 사전은 어떻게 만들어지는 건가요?" 박시니어 씨가 tok_train.py 파일을 열었습니다.

"직접 학습시켜 보면 이해가 될 거예요."

tok_train.py는 텍스트 데이터로부터 BPE 토크나이저를 학습시키는 스크립트입니다. 빈도 기반으로 병합 규칙을 생성하고, 지정된 어휘 크기에 도달할 때까지 반복합니다.

학습 결과는 모델 파일로 저장됩니다.

다음 코드를 살펴봅시다.

import collections

def train_bpe(text: str, vocab_size: int) -> tuple[dict, list]:
    # 1단계: 바이트 단위로 초기화 (256개 기본 토큰)
    vocab = {bytes([i]): i for i in range(256)}
    merges = []

    # 바이트 시퀀스로 변환
    tokens = [bytes([b]) for b in text.encode('utf-8')]

    # 2단계: 목표 어휘 크기까지 반복
    while len(vocab) < vocab_size:
        # 가장 빈번한 쌍 찾기
        pairs = collections.Counter(zip(tokens[:-1], tokens[1:]))
        if not pairs:
            break
        best_pair = pairs.most_common(1)[0][0]

        # 새 토큰 생성 및 병합
        new_token = best_pair[0] + best_pair[1]
        vocab[new_token] = len(vocab)
        merges.append(best_pair)

        # 토큰 리스트 업데이트
        tokens = merge_tokens(tokens, best_pair, new_token)

    return vocab, merges

토크나이저 학습은 생각보다 직관적인 과정입니다. 마치 자주 쓰는 단축어를 만드는 것과 같습니다.

"ㅋㅋㅋ"를 자주 쓰면 이를 하나의 단위로 인식하게 되죠. BPE 학습도 마찬가지로, 자주 등장하는 패턴을 찾아 새로운 토큰으로 등록합니다.

1단계에서는 기본 어휘를 초기화합니다. 0부터 255까지의 모든 바이트 값을 기본 토큰으로 등록합니다.

이것이 바이트 수준 BPE의 시작점입니다. 어떤 텍스트든 바이트로 표현할 수 있으므로, 이 256개 토큰만으로도 모든 텍스트를 인코딩할 수 있습니다.

2단계에서 본격적인 학습이 시작됩니다. 입력 텍스트에서 연속된 두 토큰의 쌍을 모두 세어봅니다.

collections.Counter가 이 작업을 효율적으로 수행합니다. 가장 빈번하게 등장하는 쌍이 첫 번째 병합 대상이 됩니다.

예를 들어 영어 텍스트에서 'e'와 ' '(공백) 쌍이 가장 자주 등장한다면, 이를 합쳐 새로운 토큰 'e '를 만듭니다. 이 새 토큰은 256번 ID를 부여받습니다.

그다음 모든 텍스트에서 이 쌍을 새 토큰으로 교체합니다. 이제 'e'와 ' '가 연속으로 등장하는 곳은 모두 'e '라는 단일 토큰이 됩니다.

이 과정을 목표 어휘 크기에 도달할 때까지 반복합니다. GPT-4는 약 10만 개, GPT-2는 약 5만 개의 어휘를 사용합니다.

merges 리스트가 왜 중요할까요? 인코딩할 때 병합 순서가 중요하기 때문입니다.

첫 번째로 학습된 병합 규칙이 첫 번째로 적용되어야 합니다. 순서가 바뀌면 완전히 다른 토큰화 결과가 나옵니다.

김개발 씨가 깨달음을 얻었습니다. "아, 그래서 같은 텍스트라도 토크나이저마다 다른 토큰으로 나뉘는 거군요!" 박시니어 씨가 맞장구쳤습니다.

"정확해요. 학습 데이터와 어휘 크기에 따라 완전히 다른 토크나이저가 만들어집니다."

실전 팁

💡 - 학습 데이터가 클수록 더 좋은 압축률을 가진 토크나이저가 만들어집니다

  • 어휘 크기는 보통 32,000에서 100,000 사이로 설정합니다

6. GPT-4 스타일 토크나이저의 비밀

마지막으로 박시니어 씨가 흥미로운 이야기를 꺼냈습니다. "GPT-4 토크나이저에는 몇 가지 비밀이 숨어 있어요." 김개발 씨의 눈이 반짝였습니다.

GPT-4 스타일 토크나이저는 기본 BPE에 여러 개선 사항을 추가한 것입니다. 정규표현식 기반 사전 분리, 특수 토큰 처리, 바이트 폴백 메커니즘 등이 포함됩니다.

이런 기법들이 모여 더 효율적이고 안정적인 토큰화가 가능해집니다.

다음 코드를 살펴봅시다.

import regex

# GPT-4 스타일 사전 토큰화 패턴
GPT4_PATTERN = r"""'(?i:[sdmt]|ll|ve|re)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]|\s+(?!\S)|\s+"""

class GPT4Tokenizer:
    def __init__(self, model_path: str):
        self.pattern = regex.compile(GPT4_PATTERN)
        self.vocab, self.merges = self._load_model(model_path)

    def encode(self, text: str) -> list[int]:
        # 1단계: 정규표현식으로 사전 분리
        chunks = self.pattern.findall(text)

        # 2단계: 각 청크에 BPE 적용
        tokens = []
        for chunk in chunks:
            chunk_tokens = self._bpe_encode(chunk)
            tokens.extend(chunk_tokens)

        return tokens

GPT-4 토크나이저가 단순한 BPE와 다른 점은 무엇일까요? 가장 큰 차이는 사전 토큰화(Pre-tokenization) 단계입니다.

BPE를 적용하기 전에 텍스트를 의미 있는 단위로 먼저 나누는 것입니다. 마치 책을 읽기 전에 목차를 훑어보는 것과 같습니다.

전체 구조를 파악한 뒤 세부 내용을 읽으면 이해가 더 쉽죠. GPT-4 토크나이저도 텍스트의 구조를 먼저 파악합니다.

GPT4_PATTERN 정규표현식을 해석해 봅시다. 첫 번째 부분 '(?i:[sdmt]|ll|ve|re)는 영어 축약형을 처리합니다.

"don't"를 "don" + "'t"로, "I'll"을 "I" + "'ll"로 분리합니다. 이렇게 하면 같은 의미의 축약형이 일관된 토큰을 받습니다.

두 번째 부분 \p{L}+는 연속된 문자를 하나의 단위로 묶습니다. "Hello"가 "Hel" + "lo"로 임의로 쪼개지는 것을 방지합니다.

세 번째 부분 \p{N}{1,3}은 숫자를 최대 3자리씩 묶습니다. "2024"는 "202" + "4"가 됩니다.

이는 연도, 가격 등의 숫자를 효율적으로 처리하기 위한 설계입니다. 왜 이런 사전 분리가 필요할까요?

순수 BPE만 사용하면 "dog."와 "dog"가 완전히 다른 토큰 시퀀스가 될 수 있습니다. 하지만 사전 분리를 거치면 "dog" + "."로 나뉘어 "dog"는 항상 같은 토큰을 받게 됩니다.

이것이 일관성입니다. 또 다른 비밀은 바이트 폴백(Byte Fallback) 메커니즘입니다.

아무리 큰 어휘 사전이라도 모든 문자 조합을 담을 수는 없습니다. 한자, 이모지, 특수 기호 등 무한한 유니코드 문자가 있기 때문입니다.

이때 바이트 폴백이 작동합니다. 알 수 없는 문자를 만나면 바이트 단위로 분해합니다.

덕분에 토크나이저가 처리하지 못하는 문자는 존재하지 않습니다. 다만 토큰 효율은 떨어지겠죠.

김개발 씨가 마지막 질문을 했습니다. "그럼 한국어는 왜 토큰을 많이 소모하나요?" 박시니어 씨가 답했습니다.

"GPT-4의 학습 데이터 대부분이 영어였기 때문이에요. 자주 등장하지 않은 한글 조합은 병합 규칙이 적어서 더 많은 토큰으로 쪼개지는 거죠." 이제 김개발 씨는 BPE 토크나이저의 모든 것을 이해했습니다.

API 비용을 줄이려면 프롬프트를 최적화해야 한다는 것도, 한국어보다 영어가 효율적이라는 것도 모두 납득이 갔습니다.

실전 팁

💡 - 한국어 전용 토크나이저를 사용하면 토큰 효율이 크게 향상됩니다

  • OpenAI의 tiktoken 라이브러리로 정확한 토큰 수를 미리 계산할 수 있습니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Python#BPE#Tokenizer#Rust#LLM#AI,LLM,Deep Learning

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.