이 글의 목표 — ① 토큰 번호가 벡터가 되는 구조 ② 단어들이 서로 정보를 주고받는 어텐션의 동작 ③ 여러 관점을 동시에 보는 방법 ④ 같은 단어가 서로 다른 의미로 갈라지는 과정을 이해합니다.
2편에서 글자가 벡터가 되는 여섯 단계를 살펴보면서, 4단계인 문맥 섞기는 뒤로 미뤄 두었습니다. 이번 편이 그 내용입니다.
이 글은 챗봇을 만드는 데 필요하지는 않습니다. 다만 "수백 글자가 어떻게 숫자 몇백 개로 압축되면서 의미를 잃지 않는가"가 궁금하셨다면, 그 답이 여기에 있습니다.
수식이 몇 개 나오지만 계산할 필요는 없습니다. 무엇을 하려는 식인지만 보시면 됩니다.
읽기 전에 — 1편과 2편의 내용을 전제로 합니다. 토큰, 벡터, 차원, 임베딩이라는 말이 낯설다면 2편을 먼저 보고 오시기를 권합니다.
2편에서 문장이 토큰으로 쪼개지고 각 토큰에 번호가 붙는다고 했습니다. 이 번호로 벡터를 꺼내 오는 곳이 임베딩 테이블입니다.
구조는 단순합니다. 세로 길이가 어휘 개수, 가로 길이가 차원 수인 거대한 표입니다.
임베딩_테이블 = [
[0.01, 0.33, ...], # 0번 토큰
[0.12, -0.08, ...], # 1번 토큰
...
[0.44, 0.21, ...], # 9823번 토큰 = "사과"
[0.31, -0.15, ...], # 9824번 토큰 = "를"
...
] # 총 3만 줄, 각 줄에 숫자 768개
어휘 3만 개에 768차원이면 숫자가 2,304만 개 들어 있는 표가 됩니다. 이 표 자체가 모델 파일의 일부이며, 1편에서 말한 가중치에 포함됩니다.
이 표의 값들은 처음에 완전히 무작위입니다. 아무 의미도 없는 난수입니다.
그 상태에서 대량의 글을 읽으며 다음 단어를 맞히는 연습을 반복합니다. 틀릴 때마다 표의 값이 조금씩 조정됩니다. 이 과정을 수없이 반복하면, 결과적으로 비슷한 자리에 등장하는 토큰끼리 비슷한 값을 갖게 됩니다.
"사과"와 "배"는 비슷한 문맥에 나오므로 비슷한 값으로 수렴하고, "사과"와 "자동차"는 다른 값으로 갈라집니다. 누가 분류표를 만들어 준 것이 아니라 저절로 그렇게 정리됩니다.
학습이 끝나면 표는 고정됩니다. 실제로 사용할 때는 번호로 해당 줄을 꺼내 오기만 합니다. 계산이 아니라 조회입니다.
| 모델 | 어휘 개수 |
|---|---|
| BERT | 30,522 |
| GPT-2 | 50,257 |
| 최신 대형 모델 | 100,000 이상 |
| LLaMA 3 | 128,256 |
중요한 점이 하나 있습니다. 이 단계에서 꺼낸 벡터는 문맥을 전혀 반영하지 않습니다.
"배를 먹었다"의 "배"와 "배가 아프다"의 "배"는 같은 번호이므로 완전히 똑같은 벡터로 시작합니다. 과일인지 신체 부위인지 구분되지 않습니다.
이 둘을 갈라놓는 것이 다음 단계입니다.
Self-Attention은 각 토큰이 문장 안의 다른 토큰들을 둘러보고, 관련 있는 쪽의 정보를 자기 벡터에 섞어 넣는 과정입니다.
사람이 글을 읽는 방식과 비슷합니다. "먹었다"를 읽을 때 우리는 무의식적으로 "무엇을 먹었지?"를 찾아 앞을 봅니다. 그 결과 "먹었다"의 의미가 "사과를 먹었다"로 구체화됩니다.
이 과정에서 각 토큰은 세 가지 역할을 동시에 맡습니다.
| 이름 | 역할 |
|---|---|
| Query (질문) | 나는 무엇을 찾고 있는가 |
| Key (간판) | 나는 어떤 정보를 가지고 있는가 |
| Value (내용) | 실제로 전달할 내용은 무엇인가 |
도서관에 비유하면 이렇습니다. Query는 내가 찾는 주제이고, Key는 책등에 붙은 제목이며, Value는 책의 실제 내용입니다. 제목을 훑어보고 관련 있어 보이는 책의 내용을 가져오는 것입니다.
"철수가 사과를 맛있게 먹었다"에서 "먹었다"를 기준으로 보겠습니다.
① "먹었다"의 Query와 각 토큰의 Key를 맞춰 봄
점수 = [2.1, 8.4, 1.3, 0.7]
철수 사과 맛있 먹었
↑ "사과를"이 가장 높음
② 점수를 0~1 사이 비율로 변환 (합이 1이 되게)
비율 = [0.08, 0.76, 0.10, 0.06]
↑ "사과를"에 76% 집중
③ 그 비율대로 각 토큰의 Value를 섞음
새로운_먹었다 = 0.08 × 철수의_내용
+ 0.76 × 사과의_내용
+ 0.10 × 맛있게의_내용
+ 0.06 × 먹었다의_내용
결과를 보시면 형태는 그대로입니다. 여전히 768개 숫자로 된 벡터 하나입니다. 그런데 값이 달라졌습니다. 이제 "먹었다"의 벡터 안에 "사과"의 정보가 76% 비율로 들어 있습니다.
즉 이 벡터는 더 이상 그냥 "먹었다"가 아니라 "사과를 먹었다"라는 맥락을 담은 벡터입니다.
이 계산이 모든 토큰에 대해 동시에 일어납니다. "사과를"도 자기 나름대로 다른 토큰들을 참고해 갱신됩니다.
실제 구현에서는 어텐션 결과를 그대로 쓰지 않습니다. 원래 입력을 다시 더해 줍니다.
결과 = 정규화( 원래값 + 어텐션결과 )
이유는 이렇습니다. 이 과정을 12번, 24번, 96번 반복하다 보면 원래 정보가 점점 희석됩니다. 복사기로 복사본을 계속 복사하면 흐려지는 것과 비슷합니다.
매번 원본을 더해 주면 "원래 값 + 새로 배운 것"이 함께 남습니다. 이 방식을 잔차 연결이라고 하며, 딥러닝 전반에서 널리 쓰이는 기법입니다. 이것이 없으면 층을 깊게 쌓을 수 없습니다.
더한 뒤에는 값의 크기를 일정 범위로 맞추는 정규화를 거칩니다. 값이 지나치게 커지거나 작아지면 학습이 불안정해지기 때문입니다.
앞의 예시에서는 "먹었다"가 "사과를"에 집중했습니다. 그런데 문장을 제대로 이해하려면 동시에 여러 관계를 봐야 합니다.
이 모두를 하나의 어텐션으로 학습하기는 어렵습니다. 그래서 여러 개를 나란히 돌립니다. 각각을 헤드라고 부르고, 이 방식을 Multi-Head Attention이라고 합니다.
흥미로운 점은 어떤 헤드가 무슨 관계를 맡을지 사람이 정하지 않는다는 것입니다. 학습 과정에서 자연스럽게 역할이 나뉩니다. 나중에 분석해 보면 어떤 헤드는 주로 주어-동사 관계를, 어떤 헤드는 수식어 관계를 보고 있는 식으로 정리되어 있습니다.
헤드를 여러 개 두면 계산량이 늘어날 것 같지만 그렇지 않습니다. 전체 차원을 나눠 갖기 때문입니다.
헤드 개수 × 헤드당 차원 = 전체 차원
12 × 64 = 768
768차원을 12개 헤드가 64차원씩 나눠 갖고, 각자 독립적으로 계산한 뒤, 결과를 다시 이어 붙이면 768차원으로 돌아옵니다.
| 모델 | 헤드 개수 | 헤드당 차원 | 전체 차원 |
|---|---|---|---|
| MiniLM | 6 | 64 | 384 |
| BERT-base | 12 | 64 | 768 |
| BERT-large | 16 | 64 | 1,024 |
| LLaMA 3 | 32 | 128 | 4,096 |
헤드당 차원이 64나 128인 것에는 이유가 있습니다. GPU는 32나 64의 배수 단위로 계산할 때 낭비 없이 처리하도록 설계되어 있습니다. 성능을 위한 실용적인 선택입니다.
어텐션 다음에는 FFN이라는 단계가 옵니다. 둘의 차이가 이 구조를 이해하는 핵심입니다.
| Self-Attention | FFN | |
|---|---|---|
| 하는 일 | 토큰끼리 정보를 주고받음 | 각 토큰을 혼자서 가공 |
| 다른 토큰 참조 | 함 | 하지 않음 |
| 비유 | 여럿이 모여 토론 | 혼자 앉아 정리 |
어텐션이 힌트를 모으는 단계라면, FFN은 그 힌트로 혼자 결론을 내리는 단계입니다.
어텐션만 반복하면 안 되는 수학적인 이유가 있습니다.
어텐션은 본질적으로 가중합입니다. 여러 값을 비율대로 섞는 것이 전부입니다. 이런 연산만 반복하면, 아무리 층을 많이 쌓아도 결국 한 층짜리와 같아집니다.
A(B(Cx)) = (ABC)x
→ 12층을 쌓아도 표현력은 1층과 동일
여기에 비선형이라 불리는 다른 종류의 연산을 끼워 넣어야 층을 쌓은 만큼 능력이 올라갑니다. FFN이 그 역할을 합니다.
"이 영화는 재미없지 않다"를 생각해 보겠습니다.
"재미없지"는 부정이고 "않다"도 부정입니다. 두 부정 벡터를 섞으면 여전히 부정 쪽의 중간값이 나옵니다. 아무리 비율을 조정해도 긍정 쪽으로 넘어갈 수 없습니다.
그런데 실제로는 부정이 두 번 겹치면 긍정입니다. 이런 규칙은 섞는 것만으로는 표현되지 않습니다. "이 조합이 나타나면 방향을 뒤집어라"라는 판단이 필요하고, 그것을 하는 것이 FFN입니다.
768차원 ──▶ 3072차원 ──▶ 걸러내기 ──▶ 768차원
펼치기 (4배) 모으기
세 단계로 나눠 보겠습니다.
펼치기 — 768차원 안에는 여러 의미 특징이 서로 뒤엉켜 있습니다. 이것을 3072차원으로 늘려 각 특징이 독립된 자리를 갖게 합니다. 좁은 방에 뒤섞여 있던 물건을 넓은 바닥에 하나씩 늘어놓는 것과 같습니다.
걸러내기 — 늘어놓은 상태에서 지금 이 토큰에 필요한 것만 남기고 나머지는 억누릅니다. 이 판단을 하는 함수가 비선형 함수이며, 앞서 말한 "방향 뒤집기"가 여기서 가능해집니다.
모으기 — 살아남은 특징만 다시 768차원으로 압축합니다. 형태는 원래대로 돌아오지만 값은 완전히 달라져 있습니다.
덧붙이면, 1편에서 말한 "패턴으로 압축된 지식"이 물리적으로 저장되는 곳도 주로 이 FFN의 가중치입니다. 연구에 따르면 사실 정보가 여기에 인코딩되어 있습니다.
지금까지의 내용을 하나의 예로 이어 보겠습니다.
"배를 너무 많이 먹어 배가 나왔다"
첫 번째 "배"는 과일이고 두 번째 "배"는 신체 부위입니다. 사람은 즉시 구분하지만, 모델에게는 완전히 같은 번호의 토큰입니다.
| 단계 | 첫 번째 배 | 두 번째 배 |
|---|---|---|
| 임베딩 테이블 조회 직후 | 완전히 동일한 벡터 — 구분 불가 | |
| 어텐션 이후 | "먹어"에 집중 → 음식 쪽으로 기울기 시작 |
"나왔다"에 집중 → 신체 쪽으로 기울기 시작 |
| FFN 이후 | 과일·음식 특징을 살리고 신체 특징을 억제 |
신체 특징을 살리고 과일 특징을 억제 |
| 여러 층 반복 후 | 완전히 다른 벡터 — 의미가 확정됨 | |
정리하면 이렇습니다. 어텐션이 주변에서 단서를 모아 오고, FFN이 그 단서로 의미를 확정합니다. 이 두 단계를 12번에서 96번 반복하면서 표현이 점점 정교해집니다.
마지막 층의 출력을 2편에서 설명한 방식으로 하나로 합치면, 그것이 문장 전체를 대표하는 벡터가 됩니다. 데이터베이스에 저장되는 그 숫자 묶음입니다.
여기까지의 과정은 임베딩 모델과 LLM이 거의 같습니다. 갈라지는 것은 마지막에 무엇을 하느냐입니다.
공통 : 토큰화 → 임베딩 테이블 → 어텐션 + FFN 반복
│
┌───────────────────────┴───────────────────────┐
▼ ▼
임베딩 모델 LLM
전부 합쳐 벡터 하나 다음에 올 토큰 하나를 고름
→ 검색에 사용 → 반복하며 문장 생성
같은 재료로 만든 두 가지 도구인 셈입니다. 그래서 RAG 시스템에는 임베딩 모델과 LLM이 둘 다 필요합니다. 하나로 대체할 수 없습니다.
이 내용을 몰라도 챗봇을 만들 수 있나요?
만들 수 있습니다. 1편부터 4편까지의 내용이면 충분합니다. 이 편은 왜 그렇게 동작하는지가 궁금한 분을 위한 것입니다. 다만 알고 있으면 문제가 생겼을 때 원인을 짐작하기 쉬워집니다. 예를 들어 긴 문장의 검색 정확도가 떨어질 때, 여러 의미가 벡터 하나에 뭉개졌기 때문이라는 짐작이 가능해집니다.
임베딩 모델과 LLM은 결국 같은 것인가요?
구조는 같은 계열이지만 목적이 다르고, 그에 맞춰 다르게 학습됩니다. 임베딩 모델은 비슷한 문장끼리 가까운 벡터를 내도록 훈련되고, LLM은 다음 단어를 잘 맞히도록 훈련됩니다. 그래서 서로 바꿔 쓸 수 없습니다.
층을 무한정 늘리면 계속 좋아지나요?
아닙니다. 어느 지점을 넘으면 성능 향상이 멈추고 계산 비용만 늘어납니다. 학습도 불안정해집니다. 3장의 잔차 연결 같은 장치가 나온 것도 층을 깊게 쌓았을 때 생기는 문제를 해결하기 위한 것입니다. 실제 모델의 층수는 실험을 통해 결정됩니다.
어텐션 계산이 무겁다고 들었습니다.
맞습니다. 모든 토큰이 모든 토큰을 살펴보므로 문장이 길어지면 계산량이 제곱으로 늘어납니다. 토큰이 두 배가 되면 계산은 네 배가 됩니다. 이것이 LLM이 한 번에 읽을 수 있는 분량에 한계가 있는 이유이며, 이 부담을 줄이는 방법이 지금도 활발히 연구되고 있습니다.
왜 하필 768이나 4096 같은 숫자인가요?
4장에서 본 것처럼 헤드 개수 × 헤드당 차원으로 정해지고, 두 값 모두 GPU 계산에 유리한 크기로 고릅니다. 절대적인 근거가 있는 숫자는 아니며, 실험으로 얻은 관행에 가깝습니다.
시리즈는 여기서 마칩니다. 1편의 "LLM은 거대한 숫자 표"라는 설명이 지금은 조금 더 구체적으로 보이실 것입니다. 그 표 안에서 어떤 계산이 일어나는지를 여기까지 따라오셨기 때문입니다.
댓글 0