KNOW 이해 단계

토큰과 임베딩이란? LLM이 언어를 숫자로 바꾸는 원리

토큰과 임베딩이란? LLM이 언어를 숫자로 바꾸는 원리

토큰(token, AI가 글을 읽고 쓰는 조각 단위로, 보통 단어 하나보다 조금 작은 단위)은 문장을 잘게 나눈 조각이고, 임베딩(embedding)은 그 조각을 의미의 지도 위 좌표로 바꾼 숫자 묶음입니다. 이 두 단계가 대규모 언어 모델(LLM, Large Language Model)이 언어를 다루는 출발점입니다. LLM은 방대한 글을 읽고 배워 문장을 이어 쓰는 AI 모델을 말합니다.

한눈에 보기

  • AI는 글을 토큰이라는 조각으로 잘라 번호를 붙인 뒤, 그 번호를 뜻이 담긴 숫자 좌표(임베딩)로 바꿔 계산합니다.
  • AI 서비스의 요금과 한 번에 넣을 수 있는 글의 길이는 글자 수가 아니라 토큰 수로 정해지고, 한국어는 같은 내용이라도 토큰이 더 많이 나옵니다.
  • 임베딩은 뜻이 비슷한 말을 가까운 자리에 놓기 때문에, 문서 검색이나 사내 자료를 찾아 답하는 AI의 바탕이 됩니다.

숫자가 되어야 계산되는 문장

이런 상황을 떠올려 보세요. 작은 무역회사에서 거래처에 보낼 안내문을 영어판과 한국어판 두 벌로 만들고, 둘 다 AI 요약 도구에 넣어 매달 정리합니다. 월말에 사용량을 보니 내용은 같은데 한국어판 쪽이 눈에 띄게 많이 잡혀 있습니다. AI가 한국어를 더 꼼꼼히 읽어서가 아닙니다. AI 서비스는 글자 수가 아니라 토큰 수로 요금을 매기는데, 한국어는 같은 뜻을 담아도 토큰이 더 잘게 쪼개져 개수가 늘어나기 때문입니다.

사람은 '고양이' 세 글자를 보고 곧바로 뜻을 떠올리지만, 컴퓨터가 다루는 것은 오직 숫자입니다. 글자든 문장이든 결국 숫자로 바꿔 넣어 줘야 합니다. 그래서 LLM의 입력 처리는 두 번의 변환을 거칩니다. 먼저 문장을 토큰으로 쪼개 각 토큰에 고유 번호를 매깁니다. 그다음 그 번호를 다시 여러 개의 실수로 이뤄진 벡터(숫자를 순서대로 늘어놓은 목록), 곧 임베딩으로 바꿉니다. 토큰화가 '조각내서 번호 붙이기'라면, 임베딩은 '번호에 뜻을 입히기'입니다.

이 글에서는 토큰화가 왜 단어가 아니라 서브워드(subword, 단어보다 작은 조각) 단위로 이뤄지는지, 임베딩이 어떻게 단어 사이의 '가깝고 멂'을 좌표로 담아내는지, 그리고 이 둘이 실무에서 컨텍스트 길이와 요금, 다국어 성능에 어떤 영향을 주는지 차례로 짚습니다. GPT를 한 번이라도 써 본 정도면 충분히 따라올 수 있습니다.

레고 블록처럼 쪼개는 토큰화

문장이 토큰 조각으로 쪼개지고, 각 조각이 고유 번호로 바뀌어 모델에 들어간다.

토큰화(tokenization)는 문장을 토큰이라는 조각으로 자르는 과정입니다. 토큰은 단어 하나일 수도, 단어의 일부일 수도, 때로는 글자 하나일 수도 있습니다. 모델은 미리 정해 둔 토큰 목록(vocabulary)의 블록마다 고유 번호를 붙여 둡니다. 문장이 들어오면 먼저 블록으로 쪼개고, 각 블록을 번호로 바꿔 모델에 넣습니다.

레고 블록에 비유하면 감이 잡힙니다. 아무리 복잡한 문장도 결국 정해진 블록들의 조합으로 표현됩니다. 예를 들어 'the weather is so good' 같은 문장은 다섯 개의 조각으로 나뉘어 각각 '3', '8921', '412', '77', '2065' 같은 번호(번호는 예시)로 바뀝니다. 이 번호 배열이 모델이 실제로 받는 입력입니다.

핵심은 토큰이 사람이 정한 문법 단위가 아니라는 점입니다. 방대한 말뭉치(corpus, 학습용으로 모은 글 묶음)에서 '자주 뭉쳐 다니는 글자 덩어리'를 통계적으로 잘라낸 조각이 토큰입니다. 그래서 토큰의 경계가 우리 직관과 어긋날 때가 잦습니다. 흔한 단어는 통째로 한 토큰이 되지만, 드문 단어나 오타는 여러 조각으로 쪼개집니다.

왜 단어가 아니라 '조각' 단위인가

가장 단순한 방법은 단어마다 번호를 하나씩 붙이는 것입니다. 하지만 이 방식은 곧 벽에 부딪힙니다. 세상의 단어는 사실상 무한합니다. 새 유행어, 고유명사, 합성어, 오타가 끝없이 등장합니다. 모든 단어에 미리 번호를 매기려 들면 목록이 감당할 수 없이 커지고, 그래도 사전에 없는 단어는 계속 나옵니다.

반대로 글자 하나하나를 토큰으로 삼으면 목록은 작아지지만, 문장을 표현하는 데 필요한 토큰 수가 너무 많아지고 의미 단위가 지나치게 잘게 부서집니다. 그래서 현대 LLM은 그 중간을 택합니다. 자주 쓰는 덩어리는 통째로 한 토큰으로, 드문 것은 잘게 쪼개는 절충입니다. 이 방식을 서브워드 토큰화라 부릅니다.

예를 들어 'cyberpunk' 같은 드문 단어가 목록에 없어도 문제없습니다. 'cyber'와 'punk'라는 두 블록을 이어 붙여 조립하면 됩니다. 정해진 수의 블록만으로도 처음 보는 단어를 표현할 수 있는 것입니다. 이 방식의 대표 알고리즘이 BPE(Byte Pair Encoding)입니다. 자주 나란히 등장하는 글자 쌍을 반복해서 하나의 블록으로 합쳐 나가며 목록을 만듭니다. OpenAI의 tiktoken이나 구글의 SentencePiece가 이런 서브워드 토크나이저(문장을 토큰으로 자르는 프로그램)를 구현한 대표 도구입니다.

한국어는 왜 조각이 더 많이 날까

언어마다 조각나는 정도가 다릅니다. 영어는 대체로 토큰 하나가 네 글자쯤이고, 토큰 하나가 영어 단어 약 0.75개에 해당합니다. 즉 100개의 토큰이면 영어 단어 75개 남짓입니다. 이 어림값은 OpenAI가 안내하는 '1 토큰 ≈ 영어 4글자 ≈ 0.75단어'라는 규칙에서 나옵니다.

한국어는 사정이 다릅니다. 같은 뜻을 담아도 토큰이 더 많이 나옵니다. 조사와 어미가 붙어 어형이 다양하게 바뀌는 데다, 토크나이저가 주로 영어 중심의 말뭉치로 학습된 탓에 한글 조각의 단위가 잘게 잡히기 때문입니다. 같은 문장을 200개 언어로 옮긴 번역문을 비교한 연구(Petrov 외, NeurIPS 2023)에서는 GPT-4의 토크나이저(cl100k_base)로 셌을 때 한국어가 영어보다 약 2.4배 많은 토큰을 썼습니다. 다만 이 배수는 토크나이저마다 다릅니다. OpenAI는 2024년 GPT-4o를 내놓으며 새 토크나이저로 예시 한국어 문장의 토큰 수가 45개에서 27개로 줄었다고 밝혔습니다. 격차는 줄고 있지만 한국어가 영어보다 토큰을 더 쓰는 경향은 남아 있습니다.

주요 토크나이저 방식을 비교하면 다음과 같습니다.

방식 대표 사용처 특징
BPE(Byte-Pair Encoding) GPT 계열(tiktoken) 자주 붙어 나오는 글자쌍을 병합해 어휘를 구성
WordPiece BERT 계열 학습 말뭉치를 가장 잘 설명하는 쪽으로 서브워드를 골라 병합
Unigram(SentencePiece로 구현) 다국어 모델(T5 등) 큰 후보 목록에서 시작해, 확률로 따져 쓸모가 적은 서브워드를 지워 나가며 어휘를 구성

이 차이는 두 가지로 체감됩니다. 첫째는 길이 제한입니다. 같은 분량의 글이라도 한국어가 컨텍스트 한도에 더 빨리 닿습니다. 둘째는 요금입니다. 사용한 토큰만큼 과금하는 API(프로그램끼리 정해진 방식으로 주고받는 창구) 서비스에서 한국어 입력·출력은 영어보다 비싸게 계산되기 쉽습니다. 한국어로 긴 문서를 다루는 실무에서는 이 '토큰 세금'을 늘 염두에 둬야 합니다.

직접 확인하는 토크나이저 실험

토큰화는 말로 설명하기보다 눈으로 확인할 때 확실히 와닿습니다. 문장을 입력하면 곧바로 조각과 조각 수를 보여 주는 공개 도구가 있습니다. OpenAI Tokenizer 같은 웹 도구는 문장을 넣으면 토큰 경계를 색으로 구분해 표시하고 총 개수를 알려 줍니다.

몇 가지 실험을 권합니다. '오늘 날씨가 참 좋다'를 넣어 몇 조각으로 나뉘는지 보고, 같은 뜻의 영어 'The weather is nice today'와 조각 수를 나란히 비교해 보세요. 대개 한국어 쪽 조각 수가 눈에 띄게 많습니다. 여기에 띄어쓰기를 바꾸거나 이모지, 오타를 일부러 섞으면 조각이 어떻게 달라지는지도 확인할 수 있습니다. 이 짧은 실험만으로도 '문장이 숫자 조각으로 바뀐다'는 개념이 추상이 아니라 손에 잡히는 사실이 됩니다. 코드로 세는 방법은 글 끝 심화 절에 적었습니다.

번호만으로는 담을 수 없는 뜻

토큰마다 고유 번호가 붙었습니다. 그런데 번호는 이름표에 지나지 않습니다. '고양이'가 5번, '강아지'가 8721번이라고 해 봅시다. 이 두 번호만 봐서는 둘이 얼마나 비슷한 뜻인지 전혀 알 수 없습니다. 5와 8721은 크기만 다를 뿐 서로 아무 관계도 없는 두 숫자입니다. 번호의 크기가 곧 의미의 크기라고 오해하면, 모델은 엉뚱한 계산을 하게 됩니다.

사람은 '고양이'와 '강아지'가 가깝고, '고양이'와 '냉장고'가 멀다는 것을 압니다. 기계도 이 '가깝고 멂'을 다룰 수 있어야 문맥을 대강이라도 파악할 수 있습니다. 다음에 올 단어를 그럴듯하게 고르려면 단어들 사이의 의미 거리를 계산할 수 있어야 하기 때문입니다.

그 답이 임베딩입니다. 토큰 하나를 숫자 하나가 아니라 숫자 여러 개의 묶음, 곧 좌표로 바꿉니다. 이 좌표들이 놓인 공간을 '의미의 지도'라 부를 만합니다. 뜻이 비슷한 토큰은 지도 위에서 가까이 모이고, 뜻이 먼 토큰은 멀리 떨어집니다. 번호가 단순한 이름표였다면, 임베딩은 위치가 곧 뜻을 말해 주는 좌표입니다.

의미의 지도 위 좌표, 임베딩

임베딩(embedding)은 각 토큰을 고정된 길이의 실수 벡터로 바꾼 것입니다. 예컨대 한 토큰을 수백에서 수천 개의 숫자로 이뤄진 벡터로 표현합니다. 이 벡터는 다차원 공간의 한 점이며, 학습을 거치면서 뜻이 비슷한 토큰끼리 가까운 좌표를 갖도록 조정됩니다.

핵심 아이디어는 '함께 쓰이는 단어는 뜻도 비슷하다'는 관찰입니다. word2vec이나 GloVe 같은 초기 단어 임베딩 연구가 이 원리를 수치로 보여 줬습니다. 방대한 글에서 어떤 단어들이 자주 이웃해 등장하는지 통계를 내면, 그 분포만으로도 단어의 의미를 좌표에 담아낼 수 있습니다. 이렇게 얻은 좌표에서는 놀라운 성질이 나타납니다. 벡터끼리 더하고 빼면 '왕 − 남자 + 여자 ≈ 여왕' 같은 유추가 근사적으로 성립합니다. 의미의 관계가 좌표의 방향과 거리로 표현되는 것입니다.

임베딩: 단어의 뜻을 벡터 공간의 좌표로 나타내, 뜻이 비슷한 단어끼리 가까이 모이는 모습

두 벡터가 얼마나 비슷한지는 보통 코사인 유사도(cosine similarity, 두 화살표가 얼마나 같은 방향을 가리키는지 재는 값)로 잽니다. 두 좌표가 이루는 각이 작을수록, 즉 방향이 비슷할수록 유사도가 1에 가까워집니다. 검색이나 추천에서 '의미가 비슷한 문장 찾기'가 바로 이 계산으로 이뤄집니다. LLM 안에서는 이 임베딩이 입력의 첫 층으로 들어가, 뒤이은 어텐션(attention) 계산의 재료가 됩니다. 어텐션은 문장 안의 각 토큰이 다른 토큰을 얼마나 참고할지를 가중치로 따져 문맥을 반영하는 장치입니다.

정적 임베딩과 문맥 임베딩의 차이

임베딩에도 세대가 있습니다. word2vec, GloVe 같은 초기 방식은 단어 하나에 좌표 하나를 고정으로 매깁니다. 이를 정적 임베딩이라 부릅니다. 문제는 한 단어가 문맥에 따라 뜻이 달라진다는 점입니다. '배'는 먹는 배일 수도, 타는 배일 수도, 신체의 배일 수도 있는데, 정적 임베딩은 이 셋에 같은 좌표를 줍니다.

트랜스포머(Transformer, 오늘날 LLM 대부분이 쓰는 신경망 구조) 기반 LLM은 여기서 한 걸음 나아갑니다. 입력 토큰의 초기 임베딩을 그대로 쓰지 않고, 어텐션을 거치며 주변 토큰의 정보를 반영해 좌표를 문맥에 맞게 다시 조정합니다. 그 결과 같은 '배'라도 문장에 따라 서로 다른 표현을 갖습니다. 이를 문맥 임베딩(contextual embedding)이라 합니다.

두 방식의 차이를 표로 정리하면 다음과 같습니다.

구분 정적 임베딩 문맥 임베딩
대표 예 word2vec, GloVe 트랜스포머 기반 LLM
좌표 결정 단어마다 고정 문장 문맥에 따라 변함
다의어 처리 한 좌표로 뭉뚱그림 뜻마다 다른 표현
계산 비용 낮음(조회만) 높음(매번 계산)
주 용도 유사어 검색, 다른 모델에 넣는 입력값 LLM 입력·이해 전반

실무에서 임베딩이라는 말은 두 갈래로 쓰입니다. 하나는 지금 설명한 LLM 내부의 입력 층이고, 다른 하나는 검색·추천에 쓰는 '문장 임베딩 API'입니다. 후자는 문장 전체를 하나의 벡터로 압축해 줍니다. RAG(검색 증강 생성, 답하기 전에 관련 문서를 먼저 찾아 붙여 주는 방식)에서 문서를 미리 임베딩해 벡터 데이터베이스(임베딩 좌표를 저장해 두고 가까운 것을 빨리 찾아 주는 저장소)에 넣어 두고, 질문이 들어오면 질문 임베딩과 가까운 문서를 찾아 함께 넣어 주는 방식이 대표적입니다.

실무에서 토큰과 임베딩을 다룰 때

실무의 첫 관문은 컨텍스트 창(context window, 모델이 한 번에 읽고 쓸 수 있는 글의 최대 길이)입니다. 모델이 한 번에 처리할 수 있는 토큰 수에는 상한이 있습니다. '128K 컨텍스트'라는 말은 보통 입력과 출력을 합쳐 최대 12만 8천 토큰까지 한 번에 다룬다는 뜻이지, 글자 수가 아닙니다. 프롬프트와 응답을 합친 총 토큰이 이 한도를 넘으면 요청이 거절되거나, 서비스에 따라 오래된 앞부분이 잘려 나갑니다. 글자 수로 어림하다가 한도를 넘기는 사고가 흔합니다. 긴 문서를 다룰 때는 미리 토큰 수를 세어 적절히 나누는(chunking) 작업이 꼭 필요합니다.

모델별 컨텍스트 창 크기: 모델마다 한 번에 다룰 수 있는 토큰의 양이 다르다

임베딩을 직접 다루는 실무라면 RAG가 대표 사례입니다. 문서를 적당한 크기로 쪼개 임베딩하고 벡터 데이터베이스에 저장한 뒤, 사용자의 질문과 코사인 유사도가 높은 조각을 검색해 프롬프트에 붙입니다. 이때 문서 조각의 크기, 임베딩 모델의 선택, 검색 결과 개수가 답변 품질을 좌우합니다.

토큰 하나에 매겨지는 가격표

상용 LLM API를 실제로 붙여 쓰다 보면 토큰이 곧 돈이라는 사실을 금방 체감합니다. 대부분의 제공사가 입력 토큰과 출력 토큰을 나눠 과금하고, 보통 출력 쪽이 몇 배 비쌉니다. 이유는 단순합니다. 입력은 한 번에 병렬로 처리하지만, 출력은 한 토큰씩 차례로 생성해야 하므로 계산 비용이 훨씬 큽니다. 그래서 같은 작업이라도 프롬프트를 길게 쓰는 편과 응답을 길게 받는 편의 비용 구조가 다릅니다.

여기서 자주 놓치는 함정이 있습니다. 앞 절에서 다뤘듯 한국어는 같은 의미를 담는 데 영어보다 토큰이 더 많이 듭니다. 같은 문서를 요약시켜도 한국어 원문은 영어 원문보다 입력 토큰이 불어납니다. 실무에서 대량 배치 작업을 돌리면 이 차이가 청구서에 그대로 쌓입니다. 비용을 아끼려면 프롬프트에서 군더더기 지시문을 걷어내고, 반복되는 시스템 프롬프트는 프롬프트 캐싱(매번 똑같이 보내는 앞부분을 저장해 두고 싸게 다시 쓰는 기능)을 쓰는 편이 낫습니다.

그래서 토큰 계산은 사소한 요령이 아니라 서비스 설계의 뼈대입니다. 예를 들어 사용자당 하루 요청 한도를 정할 때, 요청 수가 아니라 누적 토큰량으로 제한을 걸어야 실제 원가와 맞아떨어집니다. RAG를 짤 때도 검색으로 끌어온 문서 조각을 몇 개까지 프롬프트에 붙일지가 곧 토큰 예산 배분 문제가 됩니다. 품질과 비용 사이의 균형점을 토큰 단위로 계산하는 감각이 실무자에게는 꼭 필요합니다.

토큰은 글자만의 개념도 아닙니다. 이미지와 음성을 함께 다루는 멀티모달(multimodal) 모델은 그림과 소리도 토큰으로 바꿔 넣습니다. 그래서 이미지 한 장이 상당한 수의 토큰을 차지합니다. 고해상도 이미지는 수백에서 수천 토큰에 해당해 컨텍스트 창과 비용을 빠르게 소진합니다. 원리는 글 끝 심화 절에서 다룹니다.

숫자를 다시 문장으로, 디코딩

같은 확률 분포라도 다음 토큰을 고르는 방식에 따라 출력의 성격이 달라진다.

토큰화와 임베딩이 문장을 숫자로 바꾸는 과정이라면, 그 반대편에는 모델이 내놓은 후보별 확률을 보고 다음 토큰을 하나씩 골라 문장을 짓는 디코딩(decoding)이 있습니다. 이때 온도(temperature) 같은 설정으로 답을 얼마나 안전하게 또는 다양하게 고를지 조절하는데, 쓰임새는 프롬프트 엔지니어링 글에서 자세히 다룹니다.

흔한 오해와 한계

첫 번째 오해는 토큰이 단어와 같다는 생각입니다. 토큰은 단어보다 잘게 쪼개진 서브워드일 때가 많고, 문법 단위와도 일치하지 않습니다. '토큰 100개'를 '단어 100개'로 여기면 컨텍스트 계산이 어긋납니다. 두 번째 오해는 토큰 번호의 크기에 의미가 있다는 착각입니다. 번호는 목록 안의 순번일 뿐, 크기 차이가 의미 차이를 뜻하지 않습니다. 뜻의 거리를 담는 것은 번호가 아니라 임베딩 벡터입니다.

임베딩에도 한계가 있습니다. 임베딩은 학습에 쓴 말뭉치의 편향을 그대로 흡수합니다. 특정 직업이나 집단에 치우친 연상이 좌표에 새겨질 수 있고, 이는 그 임베딩을 가져다 쓰는 검색·분류 같은 작업의 편향으로 이어집니다. 또 임베딩 공간의 '거리'가 언제나 우리가 원하는 의미의 유사성과 일치하지는 않습니다. 겉으로 드러난 표현이 비슷해 좌표가 가까워도 실제 뜻은 다를 수 있습니다.

다국어에서도 주의가 필요합니다. 영어 중심으로 학습된 토크나이저와 임베딩은 한국어를 비롯한 비영어권 언어에서 토큰이 잘게 쪼개지고 표현이 덜 정교해지는 경향이 있습니다. 최근 모델들이 이 격차를 줄이고 있지만, 언어마다 성능과 비용이 다르다는 점은 여전히 실무의 변수입니다.

더 깊이 들어가려면

토큰화의 기초를 다지려면 서브워드 알고리즘의 원조인 BPE 논문(Neural Machine Translation of Rare Words with Subword Units)과, 언어에 묶이지 않는 토크나이저를 다룬 SentencePiece를 읽어 볼 만합니다. OpenAI가 공개한 tiktoken 저장소를 직접 돌려 보면 GPT 계열이 실제로 어떻게 문장을 쪼개는지 눈으로 확인할 수 있습니다.

임베딩의 원리는 word2vec을 소개한 'Efficient Estimation of Word Representations'와 그 확장인 'Distributed Representations of Words and Phrases', 그리고 통계적 접근을 취한 GloVe 논문에서 뿌리를 확인할 수 있습니다. 벡터 유추와 코사인 유사도가 어떻게 의미를 담는지 이 자료들이 잘 보여 줍니다.

다음 단계는 어텐션과 트랜스포머입니다. 임베딩이 어텐션을 거치며 문맥 임베딩으로 바뀌는 과정, 그리고 그 결과가 다음 토큰의 확률 분포로 이어지는 흐름까지 이해하면 LLM이 '다음 단어를 고르는' 전체 그림이 완성됩니다. 토큰화와 임베딩은 그 긴 여정의 첫 두 정거장입니다.

실전에 적용하려면

문서에서 개체와 관계를 뽑아 엮으면 검색을 넘어 지식그래프로 확장할 수 있습니다. 젠아이랩스(GenAI Labs)의 지식그래프·온톨로지 솔루션이 이 구조화를 기업 데이터에 맞춰 구축해 드립니다.

개발자를 위한 심화

이 부분은 직접 구현하는 개발자를 위한 내용입니다.

토크나이저로 토큰 수 세기

아래는 파이썬 tiktoken 라이브러리로 문장의 토큰 번호 배열을 얻는 코드입니다. o200k_base는 GPT-4o 이후 모델이 쓰는 토크나이저이고, GPT-4는 cl100k_base를 씁니다.

import tiktoken

enc = tiktoken.get_encoding('o200k_base')
ids = enc.encode('오늘 날씨가 참 좋다')
print(ids, len(ids))  # 토큰 번호 배열과 토큰 수

배열의 길이가 곧 토큰 수입니다. 개발 단계에서 이렇게 실제 토큰 수를 재 보는 습관이 비용 관리에 도움이 됩니다.

디코딩 방식

모델은 매 스텝마다 어휘 사전 전체의 확률 분포를 내놓습니다. 확률이 가장 높은 토큰을 매번 고르는 그리디 디코딩(greedy decoding)은 재현 가능하지만 같은 표현을 되풀이하기 쉽고, 여러 후보 경로를 함께 유지하는 빔 서치(beam search)는 번역처럼 정답에 가까운 출력이 있는 작업에 맞습니다. 챗봇은 보통 분포에서 무작위로 뽑는 샘플링(sampling)을 쓰는데, 이때 로짓(logit, 확률로 바뀌기 전 모델이 각 토큰에 매긴 원점수)을 온도로 나눈 뒤 확률로 바꿉니다. 온도가 낮으면 분포가 뾰족해지고 높으면 평평해집니다. top-k는 상위 k개 후보만, top-p는 확률을 높은 순으로 더해 임계값 p에 닿을 때까지의 후보만 남깁니다. 작업별 설정값은 프롬프트 엔지니어링 글을 참고하세요.

임베딩 거리와 벡터 검색 색인

코사인 유사도는 방향이 같으면 1, 직각이면 0, 정반대면 -1이 나옵니다. 유클리드 거리(직선 거리)가 아니라 각도를 보는 까닭은 벡터의 길이가 뜻과 관계없는 요인에 휘둘리기 쉽기 때문입니다. 단어 등장 횟수를 세어 만드는 옛 방식의 문서 벡터라면 '고양이'가 한 번 나온 문장과 열 번 나온 문장은 벡터 길이가 크게 다르지만 방향은 비슷합니다. 지금의 신경망 임베딩은 이렇게 횟수를 그대로 세지 않지만, 방향만 비교하는 코사인 유사도를 기본 척도로 쓰는 관행은 그대로 이어집니다. 실제로 임베딩을 미리 정규화(normalize)해 길이를 1로 맞춰 두면, 코사인 유사도와 내적(dot product)이 같아져 계산도 빨라집니다.

벡터가 수백만 개로 불어나면 매번 전수 비교를 할 수 없습니다. 여기서 근사 최근접 이웃 탐색(ANN, Approximate Nearest Neighbor)이 등장합니다. HNSW 같은 그래프 기반 색인이나 IVF 같은 군집 기반 색인을 써서, 정확도를 조금 양보하는 대신 검색 속도를 크게 끌어올립니다. FAISS(라이브러리), pgvector(Postgres 확장), Milvus·Qdrant(벡터 데이터베이스) 같은 도구가 이런 색인을 제공합니다. RAG 시스템의 검색 품질과 지연 시간은 결국 이 색인 설정에서 갈립니다.

한 가지 주의할 점은 서로 다른 모델이 만든 임베딩을 섞어 쓰면 안 된다는 것입니다. 임베딩 공간은 모델마다 좌표계가 다릅니다. A 모델로 문서를 색인해 두고 B 모델로 질의를 임베딩하면 좌표계가 어긋나 엉뚱한 결과가 나옵니다. 검색 인덱스를 만든 모델과 질의를 임베딩하는 모델은 반드시 같아야 하고, 임베딩 모델을 바꾸면 전체 인덱스를 다시 만들어야 합니다.

토크나이저가 만들어내는 눈에 안 보이는 버그

토큰화는 조용히 성능을 갉아먹는 버그의 온상입니다. 대표적인 사례가 숫자 처리입니다. 초기 BPE 토크나이저는 큰 수를 일관되지 않게 쪼갰습니다. '1234'가 통째로 한 토큰이 될 수도 있고 '12'와 '34'로 갈릴 수도 있어, 모델이 자릿수 개념을 잡기 어렵습니다. 이 때문에 LLM이 여러 자리 덧셈에서 종종 틀립니다. LLaMA처럼 숫자를 한 자리씩 쪼개도록 토크나이저를 정한 모델이 나온 것도 이런 산술 오류를 줄이려는 조치입니다.

공백과 대소문자도 함정입니다. 많은 토크나이저가 앞에 공백이 붙은 토큰과 붙지 않은 토큰을 다르게 취급합니다. 예를 들어 문장 첫머리의 'Hello'와 문장 중간의 ' Hello'가 서로 다른 토큰 번호를 갖습니다. 프롬프트 끝에 실수로 공백을 하나 더 넣으면 모델이 이어 붙일 토큰의 후보 분포가 미묘하게 바뀝니다. 출력을 세밀하게 통제해야 하는 작업에서는 이런 사소한 차이가 결과를 흔듭니다.

이른바 '글리치 토큰(glitch token)' 현상도 유명합니다. 학습 말뭉치에는 거의 나오지 않지만 토크나이저 사전에는 등록된 희귀 토큰들이 있는데, 이런 토큰을 입력에 넣으면 모델이 앞뒤가 안 맞는 이상 행동을 보입니다. 웹 크롤링 데이터에 섞인 특정 사용자명이나 자동 생성된 문자열이 토크나이저를 만들 때는 자주 등장해 사전에 올랐지만, 정작 본 학습 단계에서는 걸러져 임베딩이 제대로 훈련되지 않은 탓입니다.

이런 문제들은 모델이 아니라 입력 단계에서 생기므로 원인을 찾기 까다롭습니다. 성능이 이상하게 나올 때는 프롬프트를 실제 토큰 열로 펼쳐 눈으로 확인하는 습관이 도움이 됩니다. 특히 코드나 표, 정형 데이터를 다룰 때는 들여쓰기 공백이 어떻게 토큰으로 묶이는지 살펴야 합니다. 탭과 스페이스가 뒤섞이면 같은 코드라도 토큰 열이 달라져 모델의 이해도가 떨어질 수 있습니다.

멀티모달 토큰: 조각이 되는 픽셀과 소리

이미지와 음성, 영상까지 한 모델이 다루면서, 서로 다른 매체를 모두 '토큰'이라는 공통 단위로 환산하는 방식이 자리 잡았습니다. 텍스트를 조각내 번호를 매기고 임베딩했듯, 이미지도 잘게 나눠 벡터로 바꾸면 트랜스포머가 똑같이 처리할 수 있습니다.

이미지 쪽에서는 그림을 격자로 잘라 작은 패치(patch)로 나눕니다. 예컨대 224×224 크기 이미지를 16×16 픽셀짜리 패치로 자르면 196개 조각이 나오고, 각 패치를 벡터로 펼쳐 임베딩합니다. 여기까지가 비전 트랜스포머(ViT)의 기본 아이디어입니다. 글과 그림을 함께 다루는 멀티모달 LLM은 이렇게 만든 패치 토큰을 텍스트 토큰과 같은 시퀀스에 이어 붙입니다. 모델 입장에서는 단어 토큰이든 이미지 패치 토큰이든 결국 벡터 열일 뿐이라, 하나의 어텐션 메커니즘으로 글과 그림을 함께 이해합니다.

음성은 파형을 짧은 구간으로 잘라 특징을 뽑거나, 아예 오디오 코덱으로 이산 토큰(discrete token)을 만들어 텍스트처럼 다룹니다. 이렇게 하면 음성 인식과 음성 합성을 하나의 언어 모델 틀에서 처리할 수 있습니다. 실제로 최근 음성 대화 모델들은 소리를 토큰 열로 바꿔 넣고 답변도 토큰으로 생성한 뒤 다시 소리로 되돌립니다.

보완 · 한눈에 보기와 한국어 요금 장면, 용어 풀이를 더하고, 토크나이저 표와 벡터 길이 설명의 사실을 바로잡았으며, 되풀이된 컨텍스트·비용 설명을 합치고 디코딩·벡터 색인·토크나이저 버그·멀티모달 원리는 개발자 심화 절로 옮겼다.

작성: AI (Claude Opus 4.8)

출처

  1. Efficient Estimation of Word Representations (word2vec)
  2. GloVe: Global Vectors for Word Representation
  3. Neural Machine Translation of Rare Words with Subword Units (BPE)
  4. SentencePiece: A simple and language independent subword tokenizer
  5. Distributed Representations of Words and Phrases
  6. tiktoken (OpenAI BPE tokenizer)
  7. Language Model Tokenizers Introduce Unfairness Between Languages · 2023-05-17
  8. Hello GPT-4o · 2024-05-13
  9. tiktoken model.py (모델별 인코딩 대응표)
뉴스레터로 매주 받아보기 기술 블로그 목록