문장 전체를 한눈에 보는 트랜스포머와 어텐션
순차 처리의 한계를 극복하고 문장 전체의 단어 간 연관성을 한 번에 파악하는 트랜스포머(Transformer)와 셀프 어텐션(Self-Attention)의 핵심 원리를 살펴봅니다.
무료 공개 · 최근 수정
순차 처리의 병목과 한 줄씩 읽는 한계
수천만 원짜리 최신 GPU 클러스터를 구비해 놓고도 연산 자원의 대부분을 놀려야 했던 시절이 있었습니다. 2010년대 중반까지 자연어 처리를 지배하던 순환 신경망(RNN)과 LSTM은 문장을 반드시 첫 글자부터 끝 글자까지 '한 단어씩 차례대로' 읽어야만 했습니다. 앞 단어의 계산이 완전히 끝나 은닉 상태(Hidden State)를 넘겨주기 전까지는 다음 단어의 계산을 시작조차 할 수 없었기 때문입니다. 이 순차적 의존 병목(Sequential Bottleneck) 때문에 수천 개의 GPU 코어는 앞선 단계가 끝나기를 기다리느라 멈춰 섰고, 문장이 100단어를 넘어가면 초반 단어의 핵심 정보가 흔적도 없이 사라지는 장기 기억 상실에 시달렸습니다.
기존 자연어 처리 모델은 번역이나 요약 작업에서 문맥이 길어지면 주어와 멀리 떨어진 동사의 호응 관계를 번번이 놓쳤습니다. 예를 들어 수십 단어 전에 등장한 주어가 지금 문장의 대명사와 어떻게 연결되는지 파악하려면 이전 상태 벡터를 계속 덮어써야 했고, 이 과정에서 과거 정보가 심각하게 왜곡되었습니다. 문맥 전체를 잃지 않으면서도 수만 개의 단어를 한꺼번에 GPU 메모리에 올려 병렬로 계산할 수 있는 새로운 신경망 구조가 절실했던 배경입니다.
2017년 구글 리서치(Google Research) 팀은 바스바니(Vaswani) 등의 논문 "Attention Is All You Need"를 통해 순환 연결을 완전히 제거한 트랜스포머(Transformer) 신경망 구조를 제안했습니다. 트랜스포머는 문장의 단어들을 순서대로 기다리지 않고 모든 토큰의 연관도를 행렬 곱셈으로 일괄 연산합니다. 추론 단계에서 토큰을 하나씩 이어 붙이는 자기회귀 생성과 달리, 모델을 훈련하거나 프롬프트를 한꺼번에 읽는 인코딩 단계에서는 문장 전체를 한 번에 병렬 연산할 수 있게 되었습니다. 이 전환을 통해 대규모 모델 훈련 속도가 비약적으로 향상되었으며, 오늘날 수천억 개의 매개변수를 지닌 대규모 언어 모델(LLM)이 탄생할 수 있었습니다.
단어 간 거리를 단숨에 좁히는 셀프 어텐션의 원리
문장 속 단어들이 서로 어떤 관계를 맺고 있는지 문장 전체를 사진 찍듯 한눈에 내려다보며 연관도를 계산하는 메커니즘을 셀프 어텐션(Self-Attention)이라고 부릅니다. 셀프 어텐션은 문장 내부의 특정 단어가 다른 모든 단어와 맺는 의미적 친밀도를 측정하여, 중요한 단어 사이에는 굵은 연결선을 긋고 무관한 단어 사이의 연결선은 옅게 만듭니다.
전형적인 예시로 "동물이 길을 건너지 않은 이유는 너무 피곤했기 때문이다(The animal didn't cross the street because it was too tired)"라는 문장을 살펴볼 수 있습니다. 이 문장에서 대명사 'it(그것)'이 가리키는 대상이 'animal(동물)'인지 'street(길)'인지 컴퓨터가 구별하는 일은 자연어 처리의 오랜 난제였습니다. 셀프 어텐션 메커니즘에서는 'it'이라는 단어를 기준으로 문장 안의 모든 단어('animal', 'didn't', 'cross', 'street', 'tired')와의 내적 점수를 계산합니다. 그 결과 'tired'와 결합할 수 있는 주체인 'animal'에 가장 높은 어텐션 가중치(예: 0.82)가 배분되고, 'street'에는 매우 낮은 가중치(예: 0.04)가 할당됩니다. 인간이 문맥을 이해할 때 자연스럽게 연결하는 논리 구조를 수학적 행렬 연산으로 정밀하게 재현하는 셈입니다.
그림: 입력 임베딩과 QKV 투영 및 어텐션 가중합으로 이어지는 계산 흐름
위 그림은 입력 텍스트가 임베딩과 위치 인코딩을 거쳐 투영되고, 소프트맥스 연산을 통해 문맥 벡터로 결합되는 과정을 보여 줍니다. 순차 루프가 없으므로 문장 전체의 행렬 연산이 단일 순방향 전파 안에서 완결됩니다.
도서관 검색 색인에 빗댄 Q, K, V 행렬
어텐션 연산은 검색 시스템의 작동 방식과 정확히 동일한 역할을 맡는 쿼리와 키와 밸류(Query, Key, Value) 세 가지 벡터 행렬의 상호작용으로 이루어집니다. 입력된 각 단어의 임베딩 벡터는 세 개의 서로 다른 학습 가능한 가중치 행렬(W_Q, W_K, W_V)과 곱해져 세 가지 고유한 표현으로 사영됩니다.
- 쿼리(Query, Q): 내가 찾고자 하는 대상이나 질문입니다. 도서관에서 사서에게 건네는 검색어 카드에 해당합니다.
- 키(Key, K): 각 데이터가 가지고 있는 고유한 색인이나 레이블입니다. 도서관 서가에 꽂힌 책 표지의 분류 기호와 주제 색인입니다.
- 밸류(Value, V): 실제 담겨 있는 지식과 정보의 실체입니다. 책장을 넘겼을 때 읽을 수 있는 도서 본문의 실질적 내용입니다.
특정 단어의 쿼리(Q)는 문장 내 모든 단어의 키(K)와 내적(Dot Product)을 수행합니다. 두 벡터가 같은 방향을 가리킬수록 내적 수치가 커지며, 이는 검색어와 책의 주제 색인이 높은 일치도를 보임을 뜻합니다. 이렇게 구한 내적 점수들을 키 벡터의 차원 수 제곱근(√d_k)으로 나눈 뒤 소프트맥스(Softmax) 함수에 통과시키면 합이 1이 되는 확률 형태의 어텐션 가중치가 완성됩니다. 마지막으로 이 가중치를 각 단어의 밸류(V) 벡터와 곱해 모두 더하면(Weighted Sum), 주변 문맥의 의미가 풍부하게 녹아든 새로운 단어 표현 벡터가 만들어집니다.
[참고] 스케일드 닷 프로덕트에서 루트 d_k로 나누는 이유
쿼리와 키의 차원 수(d_k)가 64나 128처럼 커질수록 두 벡터의 내적 결과값은 분산이 커져 극단적으로 큰 양수나 음수가 될 수 있습니다. 값이 지나치게 커진 상태로 소프트맥스 함수에 들어가면 특정 원소의 확률만 1에 수렴하고 나머지 기울기(Gradient)가 0에 가까워지는 기울기 소실 현상이 발생합니다. 따라서 표준편차를 일정하게 유지하도록 값들을 √d_k로 나누어 안정적인 역전파 학습을 유도합니다.
수학적으로 어텐션 공식은 다음과 같이 간결한 행렬 수식으로 정의됩니다.
Attention(Q, K, V) = softmax(Q × K^T / √d_k) × V이 연산은 개별 단어 단위로 따로 수행되는 것이 아니라, 문장 안의 모든 단어를 모아놓은 거대한 Q, K, V 행렬 단위로 한 번에 계산되므로 행렬 곱셈에 최적화된 GPU에서 극한의 가속 성능을 발휘합니다.
다양한 관점을 동시에 포착하는 멀티헤드 어텐션
단일 어텐션 연산만으로는 문장 속에 공존하는 문법적 구조, 대명사 지칭 관계, 수식어 호응 같은 복합적 의미를 한꺼번에 포착하기 어렵기 때문에 여러 개의 어텐션을 병렬로 배치한 멀티헤드 어텐션(Multi-Head Attention)을 적용합니다. 쿼리, 키, 밸류 벡터를 하나의 큰 차원으로 연산하는 대신 h개의 작은 부분 공간(예: 8개 또는 16개 헤드)으로 쪼개어 각각 독립적인 어텐션을 수행하는 방식입니다.
예를 들어 8개의 헤드를 사용하는 모델이라면 다음과 같이 관점이 분담됩니다.
- 헤드 1: 문장의 주어와 서술어 사이의 문법적 결합 관계에 집중합니다.
- 헤드 2: 대명사가 가리키는 선행 명사가 무엇인지 문맥을 추적합니다.
- 헤드 3: 시제와 조건절의 시간적 선후 관계를 분석합니다.
- 헤드 4~8: 감정 표현, 수식 관계, 특정 도메인 전문 용어의 결합 강도를 각각 감지합니다.
각 헤드가 독립적으로 계산한 h개의 결과 벡터는 옆으로 나란히 결합(Concatenate)된 뒤, 최종 선형 투영 행렬(W_O)을 거쳐 원래 차원으로 통합됩니다. 이 구조 덕분에 모델은 단일 문장 안에서도 여러 층위의 언어적 맥락을 손실 없이 입체적으로 학습합니다.
순수 파이썬 코드로 어텐션 가중치 계산해 보기
외부 딥러닝 프레임워크 없이 기본 파이썬 연산만으로 쿼리와 키의 내적, 소프트맥스 정규화, 밸류 가중합이 일어나는 핵심 과정을 직접 확인할 수 있습니다.
import math
# 세 단어로 이루어진 작은 문장의 Q, K, V 임베딩 벡터 (차원 수 d_k = 2)
# 단어 순서: [0: '동물이', 1: '길을', 2: '건넜다']
queries = [[1.2, 0.4], [0.1, 1.5], [0.8, 0.9]]
keys = [[1.1, 0.3], [0.2, 1.4], [0.7, 0.8]]
values = [[2.0, 0.5], [0.5, 3.0], [1.5, 1.5]]
d_k = len(keys[0])
scale = math.sqrt(d_k)
# 1. 0번 단어('동물이')의 쿼리와 모든 단어 키의 내적 점수 계산
scores = []
q0 = queries01. For k in keys:
dot_product = sum(q_elem * k_elem for q_elem, k_elem in zip(q0, k))
scores.append(dot_product / scale)
# 2. 소프트맥스를 이용한 확률 가중치 변환
exp_scores = [math.exp(s) for s in scores]
sum_exp = sum(exp_scores)
attention_weights = [s / sum_exp for s in exp_scores]
# 3. 밸류 벡터와의 가중합 계산 (최종 문맥 벡터 도출)
context_vector = [0.0] * len(values[0])
for weight, val in zip(attention_weights, values):
context_vector[0] += weight * val01. Context Vector[1] += weight * val02. Print("어텐션 가중치:", [round(w, 3) for w in attention_weights])
print("결합된 문맥 벡터:", [round(c, 3) for c in context_vector])출력 결과는 다음과 같습니다.
어텐션 가중치: [0.407, 0.259, 0.334]
결합된 문맥 벡터: [1.445, 1.481]실행 출력에서 0번 단어의 쿼리는 자신의 키 벡터와 가장 높은 내적 점수를 기록하여 40.7%의 가중치를 가져가고, 동사 '건넜다'와도 33.4%의 유의미한 연관도를 형성합니다. 이 가중치를 바탕으로 도출된 최종 문맥 벡터는 단순한 단어 사전의 고정 벡터가 아니라, 문장 내 다른 단어들의 의미 정보가 가중합으로 스며든 동적 문맥 벡터가 됩니다.
트랜스포머의 계산 복잡도와 컨텍스트 창의 한계
셀프 어텐션의 압도적인 문맥 파악 능력에는 연산량과 메모리 사용량이 입력 토큰 수 N의 제곱에 비례하여 증가하는 O(N²) 복잡도라는 명확한 대가가 따릅니다. 문장 길이가 두 배로 늘어나면 단어 간의 쌍(Pair) 비교 횟수는 네 배로 폭증합니다.
예를 들어 1,000토큰의 문맥을 처리할 때는 1,000,000번의 어텐션 행렬 요소가 계산되지만, 128,000토큰의 긴 대화를 한 번에 다루려면 약 160억 개가 넘는 어텐션 셀이 동시에 메모리에 로드되어야 합니다. 최신 모델들이 어텐션 연산 최적화 기법(FlashAttention)이나 질의 그룹 어텐션(Grouped-Query Attention; GQA)을 적극적으로 도입하는 이유도 바로 이 2차 복잡도 병목을 완화하기 위함입니다. 이러한 하드웨어적 제약을 극복하는 원리를 이해해야 실제 프롬프트 설계와 에이전트 시스템에서 토큰 효율을 극대화할 수 있습니다.
참고 문서
설명이 어렵거나 잘못된 부분을 발견하셨나요?
문서 수정 의견 보내기