//
밑바닥부터 만들면서 배우는 LLM · 세바스찬 라시카
트랜스포머가 등장하기 전, 언어 번역에서 가장 인기 있던 구조는 **순환 신경망(RNN)**의 인코더-디코더 모델이었다. 인코더가 입력 전체를 하나의 은닉 상태로 압축하면, 디코더가 그 상태만 보고 출력을 한 토큰씩 생성하는 구조다.
이 구조의 치명적인 약점은 명확하다. 입력 시퀀스 전체를 단 하나의 고정 크기 벡터에 욱여넣어야 한다는 것. 짧은 문장에서는 잘 동작하지만, 긴 문장이 들어오면 앞쪽 정보가 압축 과정에서 사라지면서 디코더가 받아 보는 컨텍스트가 부실해진다.
이 병목을 해결하기 위해 2014년에 **바흐다나우 어텐션(Bahdanau Attention)**이 등장했고, 3년 뒤인 2017년에 RNN 자체를 통째로 걷어내고 어텐션만으로 시퀀스를 모델링하는 트랜스포머가 나온다.
셀프 어텐션은 트랜스포머 기반 모든 LLM의 핵심이다. "셀프"라는 이름은, 하나의 입력 시퀀스 내부에서 서로 다른 위치의 원소들 사이의 어텐션 가중치를 계산한다는 데서 온다.
목표는 단순하다. 입력 시퀀스의 각 원소 x에 대해, 시퀀스 내 다른 모든 원소의 정보를 통합한 문맥 벡터(context vector) z를 계산하는 것.
"이 단어는 문장의 다른 어떤 단어들과 얼마나 관련이 있는가?"
이 질문에 대한 답이 어텐션 가중치이고, 가중치를 사용해 만든 표현이 문맥 벡터다.
가중치 행렬 없이 어텐션이 어떻게 동작하는지부터 손으로 따라가 본다. 입력 시퀀스가 3차원 벡터로 임베딩되어 있다고 가정하자.
1. 어텐션 점수 계산: 두 벡터의 닷-프로덕트(내적)
score(i, j) = x_i · x_j
2. 어텐션 가중치 계산: softmax로 정규화
α(i, j) = softmax_j( score(i, j) )
3. 문맥 벡터 계산: 가중합
z_i = Σ α(i, j) · x_j
**닷-프로덕트(내적)**는 두 벡터를 원소끼리 곱해 모두 더하는 연산이다. 두 벡터가 비슷한 방향을 가리킬수록 큰 값이 나오므로, 이 자체가 일종의 유사도 측정이 된다. 그런 다음 소프트맥스로 점수를 합 1의 가중치로 만들고, 입력 벡터에 가중치를 곱해 합치면 문맥 벡터가 나온다.
이 절차를 입력의 모든 토큰에 대해 반복하면, 시퀀스 길이만큼의 문맥 벡터가 만들어진다.
GPT를 비롯한 실제 LLM에서 쓰는 셀프 어텐션은 학습 가능한 세 개의 가중치 행렬을 도입한다. 이를 **스케일드 닷-프로덕트 어텐션(Scaled Dot-Product Attention)**이라 부른다.
세 가중치 행렬의 역할은 다음과 같다.
| 행렬 | 역할 |
|---|---|
| W_Q (Query) | "내가 무엇을 찾고 있는가" — 검색 쿼리 |
| W_K (Key) | "내가 어떤 정보를 가지고 있는가" — 데이터베이스의 키 |
| W_V (Value) | "내가 제공할 실제 정보" — 키에 매핑된 값 |
각 토큰에 대해 Q, K, V를 만든 다음, Q와 K의 닷-프로덕트로 어텐션 점수를 계산하고, 차원 수의 제곱근으로 나누어 스케일링한다(점수의 분산이 너무 커지지 않게 하는 안정화 트릭). 그 다음 소프트맥스를 거치면 어텐션 가중치 행렬이 완성된다. 마지막으로 어텐션 가중치를 V에 곱하면 문맥 벡터가 나온다.
수식으로는 한 줄이다.
GPT는 다음 토큰을 예측하는 모델이다. 학습 시 위치 i의 토큰을 예측할 때 위치 i+1, i+2, ...의 토큰을 미리 보고 답을 맞추면 안 된다. 이를 막는 장치가 코잘 어텐션(Causal Attention) 혹은 **마스크드 어텐션(Masked Attention)**이다.
핵심은 어텐션 점수 행렬의 상삼각(upper triangular) 영역을 0으로 만드는 것이다.
원본 어텐션 행렬 (4×4): 마스킹 후:
[ a11 a12 a13 a14 ] [ a11 0 0 0 ]
[ a21 a22 a23 a24 ] → [ a21 a22 0 0 ]
[ a31 a32 a33 a34 ] [ a31 a32 a33 0 ]
[ a41 a42 a43 a44 ] [ a41 a42 a43 a44 ]
실제 구현에서는 0으로 만들기 전에 소프트맥스에 들어갈 자리를 -∞로 채운 다음 소프트맥스를 적용한다. 그러면 -∞에 해당하는 위치의 가중치가 0이 되고, 나머지 위치만으로 가중치가 합 1이 되도록 정규화된다.
코잘 마스킹 외에 학습의 안정성을 위해 **드롭아웃(Dropout)**도 함께 적용한다. 어텐션 가중치를 계산한 후(또는 V에 적용한 후) 일부 가중치를 랜덤하게 0으로 만드는 기법으로, 모델이 특정 어텐션 경로에 과도하게 의존하지 않도록 한다. 과적합 방지의 표준 도구다.
지금까지 본 셀프 어텐션은 **싱글 헤드(single-head)**다. 한 번의 Q/K/V 연산으로 한 가지 방식의 어텐션 가중치만 학습할 수 있다는 뜻이다. 하지만 자연어에는 다양한 종류의 관계가 동시에 존재한다. 통사 관계, 의미 관계, 공참조(coreference), 인접 관계 등.
**멀티 헤드 어텐션(Multi-Head Attention)**은 이를 해결하기 위해 어텐션을 여러 개 병렬로 수행한다.
구현 방식은 두 가지다.
GPT-2 124M의 경우 헤드 수는 12개, 임베딩 차원이 768이므로 각 헤드의 차원은 768 / 12 = 64다. 12개의 헤드가 각자 다른 관계 패턴을 학습한 다음, 마지막에 모든 헤드의 출력을 이어 붙이고 한 번 더 선형 투영해 최종 문맥 벡터를 만든다.
헤드를 늘릴수록 표현력이 좋아지지만, 각 헤드의 차원은 작아진다(임베딩 차원을 헤드 수로 나누므로). 그래서 헤드 수와 헤드 차원은 일종의 트레이드오프 관계다. 최근 모델에서 등장한 **GQA(Grouped Query Attention)**나 **MQA(Multi-Query Attention)**는 K/V만 헤드 수를 줄여 메모리/속도를 최적화하는 변형들이다.
3장은 책 전체에서 가장 밀도 높은 부분이다. 가중치 없는 단순 어텐션 → 학습 가능 가중치(Q/K/V) → 코잘 마스킹 → 드롭아웃 → 멀티 헤드의 순서로 한 단계씩 쌓아 올리면, 하나의 멀티 헤드 어텐션 모듈이 완성된다. 이 모듈이 다음 장에서 만들 트랜스포머 블록의 절반을 차지하게 된다.
이 글이 도움이 되셨나요?