NVIDIA NCA-GENL

NVIDIA NCA-GENL 시험 노트개념 정리16 MIN

토큰화와 임베딩

BPE·WordPiece·SentencePiece가 단어를 쪼개는 방식, OOV와 어휘 크기, 컨텍스트 길이를 토큰으로 계산하는 법, 정적 임베딩과 문맥 임베딩의 차이와 코사인 유사도를 정리합니다.

모델은 글자를 읽지 않습니다. 텍스트는 토큰이라는 단위로 잘려 번호가 되고, 그 번호가 벡터로 바뀐 다음에야 신경망에 들어갑니다. 이 두 걸음이 토큰화와 임베딩입니다. 비용도 한계도 여기서 정해집니다 — 컨텍스트 길이도 API 요금도 토큰 단위로 세고, 검색과 RAG가 「비슷하다」고 판단하는 근거도 임베딩 벡터입니다.

토큰이라는 단위

글자·단어·서브워드

토큰화(tokenization)는 텍스트를 모델이 다루는 단위로 쪼개는 일입니다. 쪼개는 단위는 셋 중 하나입니다.

글자 단위는 어휘가 수백 개로 아주 작고 모르는 단어가 없지만, 시퀀스가 길어져 어텐션 비용이 제곱으로 붙고 한 토큰이 담는 의미가 거의 없습니다. 단어 단위는 의미 단위와 맞아떨어지지만 어휘가 수십만으로 불어나고 「먹었습니다」와 「먹었고」를 전혀 다른 항목으로 봅니다.

그래서 실제로 쓰는 것이 서브워드(subword)입니다. 자주 나오는 덩어리는 통째로 한 토큰으로 두고 드문 단어는 조각으로 쪼갭니다. 「tokenization」이 token + ization으로 갈리는 식이라, 어휘를 3만~15만 정도로 묶으면서도 처음 보는 단어를 표현할 수 있습니다.

OOV와 어휘 크기

OOV(out-of-vocabulary)는 어휘에 없는 단어를 만나는 상황입니다. 단어 단위 토크나이저는 이때 <UNK> 같은 자리표로 바꿀 수밖에 없어 그 단어의 정보를 통째로 잃습니다. 신조어·고유명사·오타가 흔한 실제 텍스트에서는 치명적입니다.

서브워드는 이 문제를 구조적으로 없앱니다. 최악의 경우 글자 단위까지 쪼개면 되므로 표현 못 하는 문자열이 원칙적으로 없습니다. 대신 어휘 크기를 정하는 판단이 남습니다 — 어휘를 키우면 문장이 짧은 토큰 열로 표현되어 추론이 싸지지만, 임베딩 테이블과 출력층이 함께 커집니다.

서브워드 알고리즘 셋

BPE의 병합 규칙

BPE(Byte Pair Encoding)는 글자 단위에서 시작해 말뭉치에서 가장 자주 함께 나오는 인접 쌍을 하나로 합치기를 정해진 횟수만큼 되풀이해 어휘를 만드는 방법입니다.

단어와 빈도가 hug 10회, pug 5회, hugs 5회, bun 4회, hugging 2회라면 쌍의 빈도는 이렇게 셉니다.

쌍 나오는 단어 빈도 합
u g hug, pug, hugs, hugging 22
h u hug, hugs, hugging 17
g s hugs 5
b u bun 4

가장 많은 u g가 첫 병합이고, 그 결과 ug가 어휘에 새 항목으로 들어갑니다. 다음 회차는 ug를 하나의 기호로 보고 다시 셉니다. GPT 계열은 글자가 아니라 바이트에서 시작하는 바이트 수준 BPE를 써서, 어떤 유니코드 문자가 와도 처리할 수 있게 했습니다.

WordPiece

WordPiece는 BERT 계열이 쓰는 방식으로, 합칠 쌍을 고르는 기준이 다릅니다. 단순 빈도가 아니라 합쳤을 때 말뭉치의 우도가 얼마나 올라가는가를 봅니다. 두 조각이 각각 흔하기만 한 쌍보다, 늘 붙어 다니는 쌍이 먼저 합쳐집니다.

표기 관습도 다릅니다. 단어 중간에 이어지는 조각에 ## 접두어를 붙여 playing을 play + ##ing으로 적습니다. 이 표기 덕분에 토큰 열만 보고도 원래 띄어쓰기를 복원할 수 있습니다.

SentencePiece

앞의 둘은 「먼저 공백으로 단어를 나눈다」를 전제로 합니다. 한국어·일본어·중국어처럼 띄어쓰기가 약하거나 없는 언어에서는 그 전제가 무너집니다.

SentencePiece는 공백까지 하나의 기호(▁)로 취급해 원문 문자열을 그대로 입력으로 받는 구현입니다. 언어별 전처리가 필요 없고, 토큰을 이어 붙이면 원문이 정확히 복원되는 무손실 성질을 가집니다. 내부 알고리즘으로 BPE나 유니그램 모델을 고를 수 있어서 「SentencePiece는 BPE의 경쟁자」가 아니라 알고리즘을 담는 그릇이라고 읽는 편이 맞습니다.

토큰 수와 컨텍스트 길이

컨텍스트 윈도라는 예산

컨텍스트 윈도(context window)는 모델이 한 번에 볼 수 있는 토큰 수의 상한입니다. 그리고 이 상한은 입력만이 아니라 입력과 출력을 합친 예산입니다. 답변으로 800토큰을 받아야 한다면 그만큼은 비워 두고 입력을 채워야 합니다.

RAG를 설계할 때 이 계산이 그대로 필요합니다. 윈도가 8,192이고 시스템 프롬프트가 320토큰, 검색해 붙일 문서가 900토큰짜리 6조각, 답변에 800토큰을 예약한다면 320+5400+800=6520320 + 5400 + 800 = 6520 이므로 1,672토큰이 남습니다. 여기서 조각을 두 개만 더 붙이면 예산을 넘겨 앞쪽이 잘려 나갑니다.

from transformers import AutoTokenizer

tok = AutoTokenizer.from_pretrained("bert-base-multilingual-cased")
print(tok.tokenize("tokenization"))   # ['token', '##ization']
print(len(tok.encode("검색 결과를 프롬프트에 붙인다")))

언어에 따라 달라지는 토큰 비용

토크나이저는 학습한 말뭉치에서 자주 본 덩어리를 통째로 어휘에 넣습니다. 영어 위주로 학습한 토크나이저는 영어 단어를 대체로 한두 토큰으로 처리하지만, 한국어는 같은 뜻을 적는 데 훨씬 많은 토큰을 씁니다.

결과는 셋입니다. 같은 내용인데 요금이 더 나오고, 컨텍스트 윈도를 더 빨리 채우며, 시퀀스가 길어 추론도 느려집니다. 다국어를 다루는 시스템에서 「글자 수 × 상수」로 토큰을 어림하면 언어마다 어긋나므로, 실제 토크나이저로 세는 것이 안전합니다.

임베딩 공간

정적 임베딩

임베딩(embedding)은 토큰 번호를 고정 길이 실수 벡터로 바꾸는 것입니다. 그 대응표가 임베딩 테이블이고 크기는 (어휘 수 × 차원)입니다. 어휘 32,000에 차원 4,096이면 32000×4096=131,072,00032000 \times 4096 = 131{,}072{,}000 개, 약 1.3억 파라미터가 임베딩에만 들어갑니다.

word2vec·GloVe·FastText 같은 초기 방식은 단어 하나에 벡터 하나를 고정으로 배정했습니다. 이것이 정적 임베딩입니다. 「왕 − 남자 + 여자 ≈ 여왕」 같은 산술이 성립하는 공간을 만들어 냈지만, 한계가 분명합니다 — 「배가 고프다」의 배와 「배를 타다」의 배가 같은 벡터를 씁니다.

문맥 임베딩

문맥 임베딩(contextual embedding)은 같은 토큰이라도 주변 문장에 따라 다른 벡터를 받는 방식입니다. 트랜스포머 층을 지나며 어텐션이 주변 토큰의 정보를 섞기 때문에, 마지막 층에서 나온 「배」의 벡터는 문장마다 다릅니다. BERT 이후 사실상 표준이 되었고, 동음이의어와 대명사가 풀리는 자리가 여기입니다.

정리하면 모델 입구의 임베딩 테이블은 정적이고, 층을 지난 뒤의 표현이 문맥적입니다. 검색·RAG에 쓰는 임베딩 모델은 이 문맥 표현을 문장 하나짜리 벡터로 모아 내주는 모델입니다.

코사인 유사도와 차원

두 벡터가 얼마나 비슷한지는 대개 코사인 유사도로 잽니다. 두 벡터가 이루는 각의 코사인이고, 방향만 보고 길이는 보지 않습니다.

cos⁡(a,b)=a⋅b∥a∥ ∥b∥\cos(a, b) = \frac{a \cdot b}{\lVert a \rVert \, \lVert b \rVert}

a=[3,4]a = [3, 4], b=[4,3]b = [4, 3] 이면 내적은 2424, 두 벡터의 크기는 각각 55 이므로 24/25=0.9624 / 25 = 0.96 입니다. 값은 −1-1 에서 11 사이이고 1에 가까울수록 같은 방향입니다. 길이를 무시하는 성질이 문서 유사도에 잘 맞습니다 — 긴 문서가 단지 길다는 이유로 더 비슷하다고 나오면 안 되기 때문입니다.

차원은 표현력과 비용의 맞바꿈입니다. 차원이 크면 미세한 차이를 담지만 저장 공간과 검색 시간이 늘고, 데이터가 적으면 그 여유를 채우지 못합니다. 검색 시스템에서는 벡터 값을 낮은 정밀도로 줄이거나 차원을 잘라 쓰는 방식으로 이 비용을 조절합니다.

연습 문제

  1. 단어와 빈도가 hug 10, pug 5, hugs 5, bun 4, hugging 2일 때 BPE가 가장 먼저 합치는 쌍은?
    ① h u
    ② u g
    ③ g s
    ④ b u
    ②. u g는 hug 10 + pug 5 + hugs 5 + hugging 2로 22회, h u는 10 + 5 + 2로 17회, g s는 5회, b u는 4회입니다.
  2. a=[3,4]a = [3, 4] 와 b=[4,3]b = [4, 3] 의 코사인 유사도는?
    ① 0.50.5
    ② 0.960.96
    ③ 1.01.0
    ④ 2424
    ②. 내적이 3×4+4×3=243 \times 4 + 4 \times 3 = 24 이고 두 벡터의 크기가 각각 55 이므로 24÷25=0.9624 \div 25 = 0.96 입니다.
  3. 어휘 32,000, 임베딩 차원 4,096인 모델의 임베딩 테이블 파라미터 수는?
    ① 약 36,096개
    ② 약 131만 개
    ③ 약 1억 3천만 개
    ④ 약 13억 개
    ③. 32,000×4,096=131,072,00032{,}000 \times 4{,}096 = 131{,}072{,}000 입니다. 어휘를 키우면 이 테이블과 출력층이 함께 커집니다.
  4. 컨텍스트 윈도가 8,192이고 시스템 프롬프트 320토큰, 검색 문서 900토큰짜리 6조각, 답변용으로 800토큰을 예약했습니다. 남는 토큰은?
    ① 672672
    ② 1,6721{,}672
    ③ 2,4722{,}472
    ④ 5,4005{,}400
    ②. 320+900×6+800=6,520320 + 900 \times 6 + 800 = 6{,}520 이므로 8,192−6,520=1,6728{,}192 - 6{,}520 = 1{,}672 입니다. 컨텍스트는 입력과 출력을 합친 예산이라 답변 몫을 빼고 세야 합니다.
  5. 띄어쓰기가 일정하지 않은 한국어 말뭉치로 토크나이저를 새로 학습하려 합니다. 원문을 그대로 입력으로 받아 공백까지 기호로 다루는 구현은?
    ① 단어 단위 토크나이저
    ② SentencePiece
    ③ 글자 단위 토크나이저
    ④ 정규식 기반 공백 분리
    ②. 공백을 ▁ 기호로 취급해 언어별 전처리 없이 원문을 그대로 받고, 토큰을 이어 붙이면 원문이 정확히 복원됩니다.
  6. 「배가 고프다」의 「배」와 「배를 타다」의 「배」가 서로 다른 벡터를 받는 방식은?
    ① word2vec 임베딩
    ② GloVe 임베딩
    ③ 트랜스포머의 문맥 임베딩
    ④ 원-핫 인코딩
    ③. ①·②는 단어마다 벡터 하나를 고정하는 정적 임베딩이라 두 「배」가 같은 벡터입니다. 어텐션이 주변 문맥을 섞어야 벡터가 갈립니다.
  7. 서브워드 토크나이저가 단어 단위 토크나이저보다 나은 점으로 알맞은 것은?
    ① 시퀀스 길이가 항상 더 짧다
    ② 어휘에 없는 단어도 조각으로 표현해 <UNK>를 피한다
    ③ 임베딩 테이블이 필요 없다
    ④ 언어에 상관없이 토큰 수가 같다
    ②. 최악의 경우 글자 단위까지 쪼갤 수 있어 표현 못 하는 문자열이 없습니다. ①은 반대이고, ④는 학습 말뭉치에 따라 크게 달라집니다.
  8. 문서 검색에서 유클리드 거리 대신 코사인 유사도를 자주 쓰는 이유는?
    ① 계산이 항상 더 빠르다
    ② 벡터의 길이를 무시하고 방향만 보므로 문서 길이 차이에 덜 흔들린다
    ③ 값이 항상 0 이상이다
    ④ 차원 수를 줄여 준다
    ②. 긴 문서가 단지 벡터가 길다는 이유로 더 가깝게 나오는 일을 피합니다. ③은 틀렸습니다 — 코사인 유사도는 −1-1 까지 내려갑니다.
NVIDIA NCA-GENL 시험 노트 전체 보기