//
LLM이 텍스트를 받아 학습 가능한 형태로 변환하기까지의 흐름은 다음 한 줄로 요약된다.
이 장은 이 파이프라인을 한 단계씩 코드로 구현해 보는 것이 핵심이다.
데이터를 벡터 형태로 변환하는 것을 **임베딩(Embedding)**이라 부른다. 초기에 가장 인기 있던 방법은 Word2Vec이다. 핵심 아이디어는 직관적이다.
"비슷한 맥락에 등장하는 단어는 비슷한 의미를 가지는 경향이 있다."
이 가정 위에서 Word2Vec은 단어를 고정 차원의 밀집 벡터로 변환하고, 의미가 유사한 단어들이 벡터 공간에서 가깝게 위치하도록 학습한다. LLM이 사용하는 토큰 임베딩도 이 발상에서 출발하지만, 사전 학습된 정적 임베딩을 가져다 쓰는 대신 모델이 학습하는 가운데 동시에 임베딩 가중치도 함께 학습한다.
정규 표현식으로 입력 문자열을 분할하면 단어 단위 토큰이 만들어진다. 이런 분할 기능을 갖춘 것을 **토크나이저(Tokenizer)**라 부른다.
토큰을 모델이 처리할 수 있는 ID로 바꾸려면 다음 절차를 거친다.
대부분의 LLM은 의미 있는 위치를 표시하기 위한 특수 토큰을 사용한다.
| 토큰 | 의미 |
|---|---|
[BOS] | 텍스트의 시작 (Beginning of Sequence) |
[EOS] | 텍스트의 끝 (End of Sequence) |
[PAD] | 배치 안에서 길이를 맞추기 위한 패딩 |
[UNK] | 어휘사전에 없는 미지의 토큰 |
GPT 계열은 이 중 일부만 쓴다. 예컨대 GPT-2는 어휘사전에 없는 단어를 만들지 않기 때문에(BPE 덕분에) [UNK]가 필요 없고, 문서 경계 표시로 <|endoftext|> 하나만 사용한다.
단어 단위 토크나이저는 어휘사전에 없는 단어를 만나면 처리할 수 없다는 치명적인 약점이 있다. **BPE(Byte Pair Encoding)**는 이 문제를 해결하기 위한 알고리즘으로, ChatGPT를 비롯한 GPT 계열 LLM의 표준 토크나이저다. 라이브러리로는 tiktoken이 대표적이다.
BPE의 핵심 아이디어는 다음과 같다.
입력: "tokenization"
어휘에 "tokenization"이 없다면 → "token" + "ization"
그것도 없다면 → "token" + "iza" + "tion"
그것도 없다면 → "t" + "o" + "k" + "e" + "n" + ...
이 덕분에 훈련 데이터에 한 번도 등장하지 않은 단어가 들어와도 BPE는 모든 텍스트를 ID 시퀀스로 변환할 수 있다. OOV(Out-Of-Vocabulary) 문제 자체가 사라지는 것이다.
LLM은 "다음 토큰을 예측"하도록 학습된다. 그래서 학습 데이터는 (입력, 타깃) 쌍의 형태여야 하는데, 이를 슬라이딩 윈도 방식으로 만든다.
원본 토큰: [t1, t2, t3, t4, t5, t6, t7, t8]
컨텍스트 길이 4, 스트라이드 1일 때:
입력 1: [t1, t2, t3, t4] 타깃 1: [t2, t3, t4, t5]
입력 2: [t2, t3, t4, t5] 타깃 2: [t3, t4, t5, t6]
입력 3: [t3, t4, t5, t6] 타깃 3: [t4, t5, t6, t7]
타깃은 입력을 한 칸 오른쪽으로 민(shifted) 시퀀스다. 즉 입력의 각 위치에서 모델은 "바로 다음에 와야 할 토큰"을 맞추는 것이 목표가 된다.
토큰 ID를 그대로 신경망에 넣을 수는 없다. 정수 ID를 고차원 벡터로 변환해야 하는데, 이 작업을 룩업(lookup) 연산이라 부른다.
어휘사전 크기: V (예: 50,257)
임베딩 차원: D (예: 768)
임베딩 가중치 행렬: W (V × D)
토큰 ID 5의 임베딩 = W[5] # W의 5번째 행을 그대로 가져옴
PyTorch의 nn.Embedding이 정확히 이 동작을 한다. 가중치 행렬을 랜덤 초기화한 뒤, 학습 과정에서 다른 파라미터와 함께 그래디언트 디센트로 업데이트된다.
여기에 한 가지 함정이 있다. 트랜스포머의 셀프 어텐션 메커니즘은 **위치에 무관(permutation-invariant)**하기 때문이다. 토큰을 섞어서 넣어도 결과가 동일하다는 뜻이다. 이는 언어처럼 순서가 의미를 결정하는 데이터에는 치명적이다.
해결책은 토큰 임베딩에 **위치 임베딩(positional embedding)**을 더해 주는 것이다. 위치 임베딩은 두 종류로 나뉜다.
| 종류 | 특징 |
|---|---|
| 절대 위치 임베딩 (GPT-2 방식) | 각 위치(0, 1, 2, ...)마다 고유한 벡터를 학습 |
| 상대 위치 임베딩 | 토큰 간의 거리만 보고 관계를 학습. 길이가 다른 시퀀스에 일반화가 잘 됨 |
GPT-2는 절대 위치 임베딩을 쓴다. 학습 가능한 별도의 위치 임베딩 행렬(컨텍스트 길이 × 임베딩 차원)을 두고, 토큰 임베딩과 같은 차원으로 더한다.
입력 임베딩 = 토큰 임베딩 + 위치 임베딩
(V→D 룩업) (위치→D 룩업)
최신 LLM(라마 시리즈 등)은 절대 위치 임베딩 대신 **RoPE(Rotary Positional Embedding)**를 쓴다. 이 책에서는 GPT-2를 재현하므로 절대 위치 임베딩을 사용하지만, "왜 옛날 방식인가"라는 질문은 자연스럽다. RoPE는 위치 정보를 임베딩에 더하는 게 아니라 쿼리/키 벡터를 회전시켜 주입하는 방식으로, 더 긴 컨텍스트로 일반화가 잘 된다.
이 장에서 만든 것은 결국 한 가지다. 텍스트 한 덩어리를 받아서, 학습 가능한 형태의 입력 임베딩 텐서로 변환해 주는 파이프라인. 이 파이프라인이 만든 텐서가 다음 장에서 다룰 어텐션 메커니즘의 입력이 된다.
이 글이 도움이 되셨나요?