[딥러닝 분석 1편] 트랜스포머 — LLM이 작동하는 방식
RNN은 단어를 하나씩 순서대로 읽었습니다. Transformer는 전체 문장을 한 번에 봅니다. 이 구조적 차이가 GPT, BERT 같은 현대 LLM의 기반이 됐습니다. Transformer가 왜 RNN보다 빠른지, 내부 구조를 정리해봤습니다.
ChatGPT, GPT-4, LLaMA — 이름은 다 달라도 내부에는 같은 구조가 들어 있습니다.
바로 Transformer인데요, 이게 왜 중요한지 구조랑 같이 정리해봤습니다. ㅎㅎ
RNN의 문제 — 앞에서부터 순서대로만 읽는다
"나는 어제 학교에서 친구를 만났다"라는 문장을 번역한다고 해봅시다.
RNN은 '나는' → '어제' → '학교에서' → '친구를' → '만났다' 순서로 딱 하나씩 읽습니다.
이전 단계의 결과를 다음 단계로 넘기는 방식이라, 반드시 순서대로 처리해야 합니다.
문제는 문장이 길어지면 앞부분 정보가 뒤로 갈수록 희미해진다는 거예요.
"나는"과 "만났다"의 관계를 파악하려면 4단계를 거쳐야 합니다.
단어가 50개짜리 문장이라면? 49단계를 거쳐야 하죠.
이 과정에서 초반 정보가 점점 흐려지는 것을 기울기 소실(Vanishing Gradient) 문제라고 합니다.
(쉽게 말하면: 문장 맨 앞의 단어 정보가 뒤로 갈수록 조금씩 희석돼서, 긴 문장에서는 처음 내용을 거의 기억 못 하게 되는 현상입니다.)
LSTM이 이 문제를 어느 정도 완화했지만, 근본적인 한계는 남았습니다.
게이트 구조 덕분에 조금 더 먼 거리의 정보를 보존할 수 있게 됐지만,
그래도 순서대로 처리해야 한다는 제약은 그대로였습니다.
그리고 이 순차 처리 방식은 GPU를 제대로 활용하지 못한다는 치명적인 단점이 있습니다.
GPU는 수천 개의 연산을 동시에 처리할 때 진가가 나오는데,
RNN/LSTM은 이전 단계가 끝나야 다음 단계를 시작할 수 있으니 GPU가 있어도 대부분 놀게 됩니다.
(쉽게 말하면: GPU는 수천 개 작업을 동시에 처리할 수 있는 병렬 처리 장치인데, RNN은 "이전 결과가 나와야 다음 단계로" 가는 순서 처리 방식이라 GPU를 제대로 못 씁니다.)
Transformer는 접근 방식이 완전히 다릅니다.
문장 전체를 한 번에 보고, 모든 단어 쌍의 관계를 동시에 계산합니다.
"나는"과 "만났다"가 아무리 멀리 떨어져 있어도, 관계를 직접 연결해서 봅니다.
덕분에 GPU의 병렬 처리 능력을 최대한 활용할 수 있게 됐고,
이것이 현대 LLM 학습이 가능해진 핵심 이유 중 하나입니다.
잠깐 — 문장이 어떻게 숫자가 되나요?
Transformer가 "문장 전체를 한 번에 본다"고 했는데, 컴퓨터는 텍스트를 직접 읽지 못합니다.
먼저 문장을 숫자 벡터로 바꾸는 두 단계가 필요합니다.
첫 번째는 토크나이징(Tokenizing)입니다.
"나는 학교에 간다"를 ["나는", "학교에", "간다"] 같은 단어(토큰) 단위로 쪼갭니다.
실제로는 단어보다 더 세밀하게 쪼개기도 합니다.
"playing" → ["play", "##ing"] 처럼 서브워드 단위로 분리하면 처음 보는 단어도 처리할 수 있습니다.
각 토큰은 어휘 사전에서 고유한 정수 ID로 변환됩니다. 예를 들어 "나는"=1042, "학교에"=3781 같은 식으로요.
두 번째는 임베딩(Embedding)입니다.
정수 ID를 고차원 벡터로 변환합니다. 예를 들어 ID 1042 → [0.2, -0.5, 0.8, ..., 0.1] (512차원).
비슷한 의미의 단어는 벡터 공간에서 가까운 위치에 놓이도록 학습됩니다.
"왕"과 "여왕"의 벡터 차이가 "남자"와 "여자"의 벡터 차이와 거의 같아지는 식이죠.
이 임베딩 벡터에 포지셔널 인코딩을 더한 값이 Transformer의 실제 입력이 됩니다.
Transformer 전체 구조 — 인코더와 디코더
Transformer는 크게 두 부분으로 나뉩니다.
인코더(Encoder)는 입력 문장을 이해하는 역할을 하고,
디코더(Decoder)는 출력 문장을 생성하는 역할을 합니다.
번역 작업을 예로 들면, 인코더가 한국어 문장을 읽고, 디코더가 영어 문장을 씁니다.
각 블록 안에는 두 가지 핵심 층이 들어 있습니다.
- Multi-Head Attention: 단어들 사이의 관계를 파악하는 층
- Feed-Forward Network (FFN): 각 위치의 정보를 개별적으로 처리하는 층
그리고 각 층 뒤에는 Add & Norm이 붙습니다.
Add는 잔차 연결(Residual Connection), Norm은 Layer Normalization입니다.
(쉽게 말하면: 잔차 연결은 "원본 신호를 바이패스로 직접 연결해두는 것"이고, Layer Normalization은 각 층을 통과할 때마다 값의 크기를 일정하게 맞춰주는 정규화 과정입니다.)
이 둘이 왜 필요한지 직관적으로 이해해봅시다.
레이어를 수십 층 쌓으면, 역전파 과정에서 기울기가 점점 작아지는 기울기 소실 문제가 생깁니다.
잔차 연결은 층을 통과한 결과에 "원래 입력값을 그대로 더해주는" 방식으로 이를 해결합니다.
층이 0을 출력하더라도 원래 입력이 그대로 다음 층으로 전달되니, 최소한 정보가 사라지지는 않습니다.
Layer Normalization은 각 층의 출력값 분포를 안정적으로 유지합니다.
층마다 값의 스케일이 제각각이면 학습이 불안정해지는데, 이를 정규화해서 학습 속도를 높이고 안정성을 확보합니다.
Add & Norm은 단순해 보이지만, 이 구조 덕분에 Transformer가 수백 층까지 쌓을 수 있게 됐습니다.
포지셔널 인코딩 — 위치 정보를 어떻게 넣나요?
Transformer는 전체를 한 번에 보기 때문에, 원래는 단어 순서를 모릅니다.
"나는 학교에 간다"와 "학교에 나는 간다"를 구분하지 못하는 거죠.
이 문제를 해결하기 위해 각 단어의 위치 정보를 별도로 더해줍니다.
사인(sin)과 코사인(cos) 함수로 만든 고유한 패턴을 단어 벡터에 추가합니다.
각 위치마다 다른 주파수의 파동이 더해져서,
모델이 "이 단어는 문장의 3번째 위치에 있다"는 것을 인식할 수 있습니다.
(쉽게 말하면: 각 위치마다 다른 음파(파동)를 단어에 덧붙이는 방식입니다. 1번 위치, 2번 위치, 3번 위치마다 서로 다른 고유한 파동이 태그처럼 붙어서 모델이 순서를 구분할 수 있습니다.)
처음 공부할 때 Positional Encoding이라는 단어가 낯설었어요. Encoding은 "정보를 특정 형식으로 변환"한다는 뜻이고, Positional은 "위치에 관한"이라는 뜻입니다. 그러니까 위치 정보를 벡터 형식으로 변환해서 단어에 더해주는 과정이에요. 단어에 순서 번호 태그를 붙이는 거라고 보면 좀 더 직관적으로 다가오더라고요.
sin/cos를 선택한 이유가 있습니다.
먼저, 학습 없이도 상대적 위치를 표현할 수 있습니다.
sin/cos는 수식으로 결정되기 때문에 별도의 학습 파라미터가 필요 없습니다.
또한 위치 i와 위치 i+k 사이의 관계를 선형 변환으로 나타낼 수 있어서,
모델이 "5번째 단어는 3번째 단어보다 2칸 뒤에 있다"는 상대적 관계를 자연스럽게 파악합니다.
학습 데이터에 없는 긴 문장에도 적용할 수 있다는 장점도 있습니다.
예를 들어 학습 시에는 최대 512 토큰 길이만 봤더라도,
이론적으로는 더 긴 위치에 대해서도 sin/cos 값을 계산해서 적용할 수 있습니다.
현대 LLM들은 여기서 한 걸음 더 나아가 RoPE(Rotary Position Embedding) 같은 변형된 방식을 사용하지만,
기본 원리는 이 아이디어에서 출발했습니다.
Multi-Head Attention — 여러 시선으로 동시에 보기
Attention 자체는 "어떤 단어에 얼마나 집중할까?"를 계산하는 메커니즘입니다.
그런데 하나의 Attention만 쓰면 한 가지 관점에서만 문장을 볼 수 있습니다.
하나의 시선만으로는 문장 안의 모든 언어적 관계를 동시에 파악하기 어렵습니다.
주어-동사 관계를 보면서 동시에 지시어-참조 관계를 파악하는 건,
하나의 집중 메커니즘으로는 충분하지 않을 수 있습니다.
Multi-Head Attention은 이 계산을 여러 번 병렬로 수행하는 방식입니다.
핵심은 각 헤드가 서로 다른 표현 공간(representation subspace)에서 Attention을 계산한다는 점입니다.
(쉽게 말하면: 입력 벡터를 다른 각도로 바라보는 "뷰포인트"라고 생각하면 됩니다. 같은 데이터를 여러 각도에서 보면 서로 다른 특징이 보이는 것처럼요.)
입력 벡터를 각 헤드별로 다른 가중치 행렬로 변환해서, 말 그대로 서로 다른 언어적 측면을 전담하게 됩니다.
헤드가 8개라면, 학습을 통해 자연스럽게 각 헤드가 다른 언어적 관계를 전담하게 됩니다.
- 1번 헤드: 주어-동사 관계 파악
- 2번 헤드: 수식어-명사 관계 파악
- 3번 헤드: 지시어-참조 관계 파악
- 나머지 헤드들: 각자 다른 패턴 학습
8개의 서로 다른 관점을 동시에 보고, 결과를 이어 붙여서(Concatenate) 선형 변환을 거치면 하나의 통합된 표현이 만들어집니다.
같은 계산량을 8개로 나눠서 병렬로 돌리기 때문에,
단순히 하나의 큰 Attention을 쓰는 것보다 훨씬 다양한 패턴을 포착할 수 있습니다.
실전 예시 — "나는 학교에 간다"를 번역하면?
인코더가 "나는 학교에 간다"를 읽으면서 각 단어의 의미와 문장 내 관계를 파악합니다.
이 과정에서 Self-Attention이 작동합니다.
"학교에"와 "간다"가 어떤 관계인지, "나는"이 전체 문장에서 어떤 역할을 하는지를
인코더 블록을 여러 번 거치며 정교하게 표현합니다.
디코더는 인코더의 출력을 참고하면서 "I", "go", "to", "school"을 하나씩 생성합니다.
디코더 안에는 두 종류의 Attention이 있습니다.
첫 번째는 Masked Self-Attention으로, 지금까지 생성한 출력 토큰들끼리의 관계를 파악합니다.
(쉽게 말하면: "아직 생성 안 된 미래 단어는 가려놓고 보지 않는" Attention입니다. 답을 미리 보면 안 되니까요.)
여기서 "Masked"의 의미가 중요합니다.
디코더가 "go"를 생성하는 시점에는 아직 "to"나 "school"을 모릅니다.
미래 토큰을 미리 보면 치팅이 되니까, 아직 생성되지 않은 위치를 마스킹해서 참조하지 못하게 막는 겁니다.
두 번째는 Cross-Attention으로, 디코더의 Query가 인코더의 Key/Value를 참조합니다.
디코더가 "go"를 생성할 때 인코더의 "간다"에 강하게 Attention하고,
"I"를 생성할 때는 인코더의 "나는"에 집중합니다.
이 어텐션 패턴 덕분에 서로 다른 언어 구조 사이에서도 정확한 번역이 가능해집니다.
코드로 확인해보기
PyTorch로 Transformer를 직접 돌려봅시다.
실습은 세 파트로 구성됩니다.
- 파트 1 — Transformer 최소 구현: PyTorch의
nn.Transformer를 사용해서 인코더-디코더 구조를 5줄로 만듭니다. 직접 숫자 벡터를 넣고 출력을 확인해보면, "Transformer가 결국 행렬 입력 → 행렬 출력을 하는 함수"라는 감이 생깁니다. - 파트 2 — 포지셔널 인코딩 시각화: sin/cos 공식으로 포지셔널 인코딩 행렬을 직접 계산하고, 히트맵으로 시각화합니다. 각 위치마다 고유한 패턴이 생기는 걸 눈으로 확인할 수 있습니다.
- 파트 3 — 간단한 번역 시뮬레이션: 짧은 토이 데이터셋으로 Transformer를 학습시켜, 입력 시퀀스를 출력 시퀀스로 변환하는 흐름을 실제로 실행해봅니다.
Jupyter 실행 결과 보기 — 파트 1: Transformer 최소 구현
Jupyter 실행 결과 보기 — 파트 2: 포지셔널 인코딩 히트맵
Jupyter 실행 결과 보기 — 파트 3: 번역 시뮬레이션
코드를 직접 실행해보면 "Transformer가 복잡한 개념이 아니라,
결국 행렬 연산을 쌓아 올린 구조"라는 것을 체감할 수 있습니다.
nn.Transformer 한 줄이면 인코더 6층 + 디코더 6층 + 모든 Attention이 세팅됩니다.
정리 — Transformer가 왜 중요한가
- 병렬 처리: 전체 문장을 한 번에 처리해서 RNN보다 훨씬 빠릅니다. GPU의 병렬 연산 능력을 제대로 활용할 수 있게 됐습니다.
- 장거리 의존성: 아무리 멀리 떨어진 단어 사이 관계도 단 한 단계만에 직접 포착합니다. RNN처럼 중간 단계를 거칠 필요가 없습니다.
- 확장성: 파라미터를 늘릴수록 성능이 꾸준히 올라갑니다. GPT, BERT, LLaMA 모두 이 구조입니다. 수백억 개의 파라미터를 가진 모델도 같은 아키텍처를 반복해서 쌓는 방식으로 만들어집니다.
Transformer는 2017년 논문 하나로 NLP 분야의 판도를 완전히 바꿨습니다.
이전까지 수십 년간 쌓아온 RNN 기반 연구들이 불과 몇 년 만에 Transformer 기반으로 교체됐고,
이제는 NLP를 넘어 이미지, 음성, 단백질 구조 예측까지 적용 범위가 넓어지고 있습니다.
다음 편에서는 Transformer에서 가장 핵심적인 부분인 어텐션 메커니즘을 다룹니다.
Q(Query), K(Key), V(Value)가 정확히 어디서 오는지, 소프트맥스로 어떻게 가중치를 만드는지,
그리고 sqrt(d_k)로 나누는 이유까지 정리해볼 예정입니다.
