Skip to main content
Summary

Attention은 관계를 보지만, 순서를 저절로 알지는 못한다

지난 장에서 우리는 Multi-Head Attention을 살펴보았다. 여러 개의 Attention head가 동시에 움직이면서 어떤 head는 문법적 관계를 보고, 어떤 head는 의미적 관계를 보고, 또 어떤 head는 멀리 떨어진 단서까지 찾아낸다. 여기까지만 보면 Transformer는 문장을 아주 잘 이해할 것 같다.

그런데 중요한 문제가 하나 남아 있다.

Self-Attention은 기본적으로 토큰 사이의 관계를 계산하는 장치이지, 토큰의 순서를 스스로 아는 장치가 아니다.

조금 이상하게 들릴 수 있다. 문장은 원래 왼쪽에서 오른쪽으로 입력되는데, 모델이 순서를 모른다니 무슨 말일까? 핵심은 Self-Attention의 계산 방식에 있다. Attention은 각 토큰 벡터를 보고 서로 얼마나 관련 있는지 점수를 매긴다. 이때 위치 정보가 없다면 모델이 보는 것은 대략 이런 것이다.

[토큰 A의 의미 벡터]
[토큰 B의 의미 벡터]
[토큰 C의 의미 벡터]

이 벡터들이 몇 번째 자리에 있었는지는 벡터 자체에 들어 있지 않다. 그래서 위치 정보를 따로 주지 않으면 모델은 토큰들을 문장이라기보다 "토큰들의 모음"에 가깝게 다루게 된다.

요리에 비유하면 이렇다. 재료 봉지 안에 양파, 고기, 마늘, 간장이 들어 있다는 사실만으로는 요리 순서를 알 수 없다. 먼저 고기를 볶고, 그다음 양파를 넣고, 마지막에 간장을 넣는 것과 처음부터 전부 냄비에 던져 넣는 것은 결과가 다르다. 문장도 마찬가지다. 어떤 단어가 있는지도 중요하지만, 어떤 단어가 먼저 나오고 어떤 단어가 나중에 나오는지가 의미를 결정한다.

위치 임베딩과 순서 정보

순서가 사라지면 문장은 이상해진다

다음 두 문장을 보자.

약을 먹고 밥을 먹었다.
밥을 먹고 약을 먹었다.

두 문장에는 거의 같은 단어가 들어 있다. 하지만 의미는 다르다. 특히 "무엇을 먼저 했는가"가 중요하다. 병원에서 약을 식후에 먹으라고 했다면 두 문장 중 하나는 맞고, 다른 하나는 틀릴 수 있다.

또 다른 예를 보자.

고양이가 먼저 도망가고 강아지가 따라왔다.
강아지가 먼저 도망가고 고양이가 따라왔다.

등장인물은 같다. "고양이", "강아지", "도망", "따라옴"이라는 재료도 비슷하다. 하지만 사건의 흐름은 완전히 달라진다. 문장은 단어 목록이 아니라 시간과 구조를 가진 배열이다.

Self-Attention은 토큰들 사이의 관련성을 계산하는 데 강력하지만, 그 계산만으로는 "이 토큰이 첫 번째인지", "저 토큰이 바로 앞에 있는지", "둘 사이가 세 칸 떨어져 있는지"를 알기 어렵다. 그래서 Transformer에는 토큰 임베딩과 별도로 위치 정보를 넣어주는 장치가 필요하다. 이것이 바로 위치 임베딩, 또는 위치 인코딩이다.

토큰 임베딩만으로는 부족하다

앞에서 우리는 토큰 ID가 임베딩 테이블을 통과해 벡터가 된다고 배웠다. 예를 들어 "밥"이라는 토큰은 어떤 벡터로, "약"이라는 토큰은 또 다른 벡터로 바뀐다.

토큰 ID → 토큰 임베딩 벡터

그런데 토큰 임베딩은 기본적으로 "이 토큰이 무엇인가"를 나타낸다. "이 토큰이 어디에 있는가"는 알려주지 않는다. 즉, "밥"이라는 토큰이 문장의 첫 번째에 나왔든 열 번째에 나왔든, 토큰 임베딩 자체는 같다.

그래서 Transformer는 보통 입력을 만들 때 토큰 임베딩에 위치 임베딩을 더한다.

입력 벡터 = 토큰 임베딩 + 위치 임베딩

이 한 줄이 매우 중요하다. 모델에게 "이 단어는 밥이야"라고 알려주는 것에 더해, "그리고 이 단어는 세 번째 자리에 있어"라는 주소표를 붙여주는 셈이다.

예를 들어 다음과 같은 문장이 있다고 하자.

나는 밥을 먹었다

토큰화 결과가 단순히 다음처럼 나왔다고 해보자.

[나는] [밥을] [먹었다]

각 토큰은 토큰 임베딩을 가진다.

[나는]   → token_embedding(나는)
[밥을]   → token_embedding(밥을)
[먹었다] → token_embedding(먹었다)

여기에 위치 임베딩을 더한다.

[나는]   → token_embedding(나는)   + position_embedding(0)
[밥을]   → token_embedding(밥을)   + position_embedding(1)
[먹었다] → token_embedding(먹었다) + position_embedding(2)

이제 모델은 같은 "먹었다"라는 토큰이라도 문장의 어느 위치에 나왔는지 구분할 수 있다. 이것이 Transformer가 단어의 모음이 아니라 문장을 처리하기 위한 첫걸음이다.

절대 위치 임베딩: 각 자리에 번호표를 붙인다

가장 직관적인 방법은 절대 위치 임베딩이다. 절대 위치란 말 그대로 "몇 번째 자리인가"를 뜻한다. 첫 번째 토큰은 0번 위치, 두 번째 토큰은 1번 위치, 세 번째 토큰은 2번 위치를 가진다.

모델 안에는 위치 임베딩 테이블이 하나 더 있다. 토큰 임베딩 테이블이 토큰 ID를 벡터로 바꾸는 표라면, 위치 임베딩 테이블은 위치 번호를 벡터로 바꾸는 표다.

위치 0 → 위치 벡터 0
위치 1 → 위치 벡터 1
위치 2 → 위치 벡터 2
위치 3 → 위치 벡터 3
...

이 방식은 매우 단순하고 강력하다. 예를 들어 모델의 컨텍스트 길이가 256이라면, 위치 임베딩 테이블은 0번부터 255번까지의 위치 벡터를 가진다. 입력 문장이 100개 토큰이라면 0번부터 99번까지의 위치 벡터를 꺼내 토큰 임베딩에 더한다.

그 결과 각 토큰의 입력 벡터에는 두 종류의 정보가 섞인다.

  • 토큰이 무엇인지에 대한 정보

  • 토큰이 몇 번째에 있는지에 대한 정보

  • 이후 Attention이 참고할 수 있는 문장 안의 자리 정보

이제 Self-Attention은 "이 토큰은 '먹었다'이고, 위치는 2번이다"라는 식의 정보를 바탕으로 관계를 계산할 수 있다. 즉, Attention이 토큰 사이의 연결을 계산하기 전에, 각 토큰에게 자기 자리표를 달아주는 것이다.

사인·코사인 위치 인코딩: 파동으로 위치를 표현한다

위치 정보를 넣는 방법은 하나만 있는 것이 아니다. Transformer를 설명할 때 자주 등장하는 방식 중 하나가 사인·코사인 위치 인코딩이다.

이 방식은 위치마다 학습되는 벡터를 따로 두지 않는다. 대신 수학 공식으로 위치 벡터를 만든다. 위치 번호를 여러 주파수의 사인과 코사인 함수에 넣어 벡터를 만든다고 생각하면 된다.

처음 보면 낯설지만, 직관은 그렇게 어렵지 않다. 사인과 코사인은 반복되는 파동이다. 어떤 파동은 빠르게 출렁이고, 어떤 파동은 천천히 출렁인다. 여러 속도의 파동을 조합하면 각 위치마다 독특한 무늬가 생긴다. 모델은 이 무늬를 보고 "아, 이 토큰은 앞쪽에 있구나", "이 토큰은 꽤 뒤쪽에 있구나"를 구분할 수 있다.

간단히 쓰면 이런 모양이다.

위치 0 → 여러 파동 값의 조합
위치 1 → 조금 달라진 여러 파동 값의 조합
위치 2 → 또 조금 달라진 여러 파동 값의 조합
...

사인·코사인 방식의 장점은 위치 벡터를 사람이 정한 공식으로 만들기 때문에 학습할 파라미터가 추가되지 않는다는 점이다. 또 이론적으로는 학습 때 보지 못한 더 긴 위치에 대해서도 공식을 적용할 수 있다. 위치 100까지 학습했더라도 공식 자체는 위치 101, 102에도 값을 만들어낼 수 있기 때문이다.

하지만 "공식으로 만들 수 있다"와 "모델이 실제로 긴 문맥에서 잘 작동한다"는 같은 말이 아니다. 모델이 짧은 문맥으로만 훈련되었다면, 갑자기 훨씬 긴 문맥을 넣었을 때 성능이 떨어질 수 있다. 위치 인코딩은 긴 문맥의 필요조건일 수 있지만, 그것만으로 충분조건이 되지는 않는다.

학습형 위치 임베딩: 모델이 위치 벡터도 직접 배운다

다른 방법은 학습형 위치 임베딩이다. 이 방식은 위치마다 벡터를 하나씩 만들고, 학습 과정에서 그 값을 조정한다. 토큰 임베딩을 학습하듯이 위치 임베딩도 학습하는 것이다.

예를 들어 컨텍스트 길이가 128이고 임베딩 차원이 64라면, 위치 임베딩 테이블은 대략 이런 크기를 가진다.

위치 임베딩 테이블 크기 = 128 × 64

0번 위치에는 64차원 벡터 하나가 있고, 1번 위치에도 64차원 벡터 하나가 있다. 처음에는 무작위 값에 가깝지만, 학습을 반복하면서 모델은 각 위치를 어떻게 표현하면 다음 토큰 예측에 도움이 되는지 배운다.

학습형 위치 임베딩은 구현이 쉽고 실제로도 잘 작동한다. 우리가 뒤에서 만들 Mini GPT에서도 이 방식을 사용할 것이다. 이유는 분명하다. 이 책의 Mini GPT는 상용 LLM을 만드는 프로젝트가 아니라 원리를 이해하기 위한 작은 모델이다. 따라서 복잡한 위치 기법보다, 토큰 임베딩에 위치 임베딩을 더하는 가장 단순한 구조가 학습 흐름을 이해하기에 좋다.

다만 학습형 절대 위치 임베딩에는 한계도 있다. 위치 테이블의 크기를 128로 만들었다면, 모델은 기본적으로 0번부터 127번 위치까지만 알고 있다. 128번째 이후의 위치 벡터는 테이블에 없다. 더 긴 문맥을 처리하려면 위치 임베딩 테이블을 늘리거나, 긴 문맥에 맞게 다시 학습하거나, 다른 위치 표현 방식을 사용해야 한다.

사인·코사인 방식과 학습형 방식의 차이

두 방식을 나란히 비교하면 차이가 더 선명해진다.

사인·코사인 위치 인코딩은 정해진 공식으로 위치 벡터를 만든다. 사람이 설계한 규칙을 모델에 제공하는 방식이다. 그래서 별도의 위치 파라미터를 학습하지 않는다. 대신 위치를 표현하는 방식이 고정되어 있다.

학습형 위치 임베딩은 위치 벡터 자체를 모델이 배운다. 모델 입장에서는 더 유연하다. 0번 위치, 1번 위치, 2번 위치가 어떤 벡터를 가져야 하는지 데이터와 손실 함수를 통해 조정된다. 대신 정해진 길이 바깥으로 나가면 곤란해진다. 테이블에 없는 자리는 바로 꺼내 쓸 수 없기 때문이다.

간단히 정리하면 이렇다.

  • 사인·코사인 방식은 공식으로 위치를 만들고, 학습할 위치 파라미터가 없다.

  • 학습형 방식은 위치 벡터를 직접 학습하고, 구현이 단순하며 유연하다.

  • 절대 위치 방식은 "몇 번째 자리인가"를 알려주지만, 긴 문맥으로 확장할 때 한계를 가질 수 있다.

작은 모델을 만들 때는 학습형 절대 위치 임베딩으로도 충분하다. 오히려 그 단순함 덕분에 모델 내부에서 어떤 일이 벌어지는지 관찰하기 쉽다.

상대 위치: 몇 번째인가보다 얼마나 떨어졌는가

절대 위치는 "이 토큰은 17번째다"처럼 각 토큰의 자리 번호를 알려준다. 그런데 언어에서는 절대 위치보다 상대적 거리가 더 중요한 경우가 많다.

예를 들어 다음 문장을 보자.

철수는 어제 산 책을 오늘 다시 펼쳤다. 그는 첫 장부터 천천히 읽었다.

여기서 "그는"이 누구를 가리키는지 찾으려면, 문장 안의 절대 위치보다 앞에 나온 명사와의 관계가 중요하다. "철수"가 전체 입력의 0번째 토큰인지 20번째 토큰인지는 덜 중요할 수 있다. 중요한 것은 "그는"보다 앞에 있고, 의미적으로 연결될 수 있는 대상이라는 점이다.

또 코드나 긴 문서에서는 "바로 앞 줄", "같은 함수 안에서 조금 전에 선언된 변수", "이 절의 첫 문장" 같은 상대적 관계가 자주 중요하다. 사람도 책을 읽을 때 "이 단어는 전체 문서의 13,472번째 글자다"라고 생각하지 않는다. 대신 "방금 전에 나온 개념", "앞 절에서 정의한 용어", "두 문단 위의 예시"처럼 상대 거리로 문맥을 잡는다.

이런 이유로 상대 위치 방식이 중요해졌다. 상대 위치 방식은 토큰의 절대 번호를 알려주는 데 그치지 않고, 토큰과 토큰 사이의 거리나 방향을 Attention 계산에 반영하려고 한다.

RoPE: 위치를 더하지 않고 회전시킨다

최근 LLM에서 자주 쓰이는 위치 표현 방식 중 하나가 RoPE다. RoPE는 Rotary Position Embedding의 줄임말로, 우리말로 풀면 회전 위치 임베딩 정도로 이해할 수 있다.

RoPE의 아이디어는 조금 독특하다. 절대 위치 임베딩처럼 입력 벡터에 위치 벡터를 단순히 더하는 대신, Query와 Key 벡터를 위치에 따라 회전시킨다. 여기서 회전이란 2차원 평면에서 화살표를 각도만큼 돌리는 것과 비슷한 연산이다. 고차원 벡터를 여러 쌍으로 나누고, 각 쌍을 위치에 맞는 각도로 돌린다고 생각하면 된다.

왜 굳이 회전시킬까? Attention 점수는 Query와 Key의 유사도로 계산된다. RoPE는 이 유사도 계산 안에 위치 차이가 자연스럽게 들어가도록 만든다. 즉, 두 토큰이 각각 어느 위치에 있는지뿐 아니라, 둘 사이가 얼마나 떨어져 있는지가 Attention 점수에 반영된다.

예를 들어 10번 위치의 토큰이 7번 위치의 토큰을 볼 때와, 100번 위치의 토큰이 97번 위치의 토큰을 볼 때를 생각해보자. 절대 위치는 다르지만 상대 거리는 둘 다 3이다. 언어적으로는 두 경우가 비슷하게 다뤄져야 할 때가 많다. RoPE 같은 방식은 이런 상대적 거리 감각을 더 자연스럽게 표현할 수 있다.

물론 RoPE가 긴 문맥 문제를 마법처럼 해결하는 것은 아니다. 모델이 아주 긴 문맥을 안정적으로 처리하려면 학습 데이터, 학습 방식, Attention 구현, 추론 최적화 등 여러 요소가 함께 맞아야 한다. 하지만 RoPE는 절대 위치 번호에만 의존하는 방식보다 상대 위치 관계를 잘 반영할 수 있어 긴 문맥을 다루는 모델에서 유리한 선택지가 된다.

위치 정보는 어디에 들어갈까

Transformer의 흐름을 다시 떠올려보자.

텍스트
→ 토큰화
→ 토큰 ID
→ 토큰 임베딩
→ Transformer Block들
→ 다음 토큰 확률

위치 정보는 보통 토큰 임베딩 직후에 들어간다.

토큰 ID
→ 토큰 임베딩 + 위치 임베딩
→ Transformer Block들

즉, Q, K, V를 만들기 전에 이미 각 토큰 벡터에 위치 정보가 섞인다. 이후 Transformer Block 안에서 이 벡터들이 Query, Key, Value로 변환되고, Attention이 계산된다.

절대 위치 임베딩을 사용하는 경우에는 구현이 정말 간단하다. 토큰 임베딩과 위치 임베딩의 차원만 같으면 그냥 더하면 된다.

x = token_embedding + position_embedding

이 단순한 덧셈이 모델에게 큰 차이를 만들어준다. 토큰만 있던 세계에 순서라는 좌표축이 생기는 것이다.

Causal Mask와 위치 정보는 다르다

여기서 한 가지 헷갈리기 쉬운 점을 짚고 가자. GPT 계열 모델은 다음 토큰을 예측할 때 미래 토큰을 보면 안 된다. 그래서 Causal Mask를 사용한다. Causal Mask는 현재 위치의 토큰이 자기보다 오른쪽에 있는 미래 토큰을 보지 못하게 막는 장치다.

그렇다면 이런 질문이 나올 수 있다.

"Causal Mask가 이미 왼쪽과 오른쪽을 구분한다면, 위치 임베딩이 없어도 되는 것 아닌가?"

아니다. Causal Mask는 볼 수 있는 범위를 제한할 뿐, 각 토큰에게 정확한 위치 정보를 넣어주지는 않는다. 커튼을 쳐서 오른쪽을 가리는 것과, 각 의자에 좌석 번호를 붙이는 것은 다른 일이다.

Causal Mask는 "현재 토큰은 이전 토큰들만 볼 수 있다"를 보장한다. 반면 위치 임베딩은 "이 토큰은 몇 번째 자리에 있고, 다른 토큰과 어느 정도 떨어져 있다"는 정보를 제공한다. 둘은 서로 보완 관계에 있다.

GPT 구조에서는 보통 둘 다 필요하다. Causal Mask는 생성 방향을 지키고, 위치 임베딩은 문장 안의 순서를 표현한다.

Mini GPT에서는 학습형 절대 위치 임베딩을 사용한다

이 책에서 만드는 Mini GPT는 원리 이해가 목적이다. ChatGPT 같은 대규모 모델을 만드는 것이 아니라, ChatGPT의 핵심 원리를 이해하기 위한 작은 GPT다. 따라서 위치 정보도 가장 이해하기 쉬운 방식으로 넣을 것이다.

우리는 학습형 절대 위치 임베딩을 사용할 것이다. 구조는 다음과 같다.

토큰 ID → 토큰 임베딩
위치 번호 → 위치 임베딩
두 벡터를 더함 → Transformer Block 입력

PyTorch 스타일로 쓰면 대략 다음과 같다.

import torch
import torch.nn as nn

class MiniGPT(nn.Module):
    def __init__(self, vocab_size, block_size, n_embd):
        super().__init__()
        self.block_size = block_size
        self.token_embedding = nn.Embedding(vocab_size, n_embd)
        self.position_embedding = nn.Embedding(block_size, n_embd)

    def forward(self, idx):
        B, T = idx.shape

        tok_emb = self.token_embedding(idx)  # (B, T, C)
        pos = torch.arange(T, device=idx.device)  # (T,)
        pos_emb = self.position_embedding(pos)  # (T, C)

        x = tok_emb + pos_emb  # (B, T, C)
        return x

여기서 idx는 토큰 ID들의 묶음이다. 모양이 (B, T)라고 되어 있는데, B는 배치 크기이고 T는 현재 입력 길이다. C는 임베딩 차원이다.

token_embedding(idx)를 통과하면 각 토큰 ID가 벡터로 바뀐다. 결과 모양은 (B, T, C)다. 예를 들어 한 번에 32개의 문장을 처리하고, 각 문장의 길이가 64토큰이며, 임베딩 차원이 128이라면 결과는 (32, 64, 128)이 된다.

위치 번호는 torch.arange(T)로 만든다. 입력 길이가 64라면 다음과 같은 숫자가 생긴다.

0, 1, 2, 3, ..., 63

이 위치 번호를 position_embedding에 넣으면 각 위치에 해당하는 벡터가 나온다. 결과 모양은 (T, C)다. 그리고 이것을 토큰 임베딩에 더한다. PyTorch는 (B, T, C)와 (T, C)를 더할 때 위치 임베딩을 배치 전체에 자동으로 맞춰 더해준다.

결과적으로 같은 위치의 모든 문장 토큰에는 같은 위치 벡터가 더해진다. 첫 번째 자리에는 0번 위치 벡터가, 두 번째 자리에는 1번 위치 벡터가 붙는다.

block_size는 위치 임베딩의 한계선이다

Mini GPT를 만들 때 block_size라는 값을 사용할 것이다. 이것은 모델이 한 번에 볼 수 있는 최대 토큰 수다. 앞에서 말한 컨텍스트 윈도우의 작은 버전이라고 생각하면 된다.

block_size = 128
position_embedding = nn.Embedding(block_size, n_embd)

이렇게 만들면 위치 임베딩은 0번부터 127번까지 존재한다. 입력 길이 T는 block_size보다 클 수 없다. 만약 200토큰짜리 입력을 넣으면 128번째 이후 위치에 해당하는 벡터가 없기 때문에 문제가 생긴다.

그래서 학습할 때는 긴 텍스트에서 block_size만큼 잘라 입력으로 사용한다. 예를 들어 전체 데이터가 아주 길어도, 학습 샘플 하나는 다음처럼 잘라낼 수 있다.

입력:  0번부터 127번까지의 토큰
정답:  1번부터 128번까지의 토큰

또 다른 위치에서 잘라 이런 샘플도 만들 수 있다.

입력:  500번부터 627번까지의 토큰
정답:  501번부터 628번까지의 토큰

흥미로운 점은 두 경우 모두 모델 내부에서는 위치 번호가 0부터 다시 시작한다는 것이다. 원본 텍스트에서 500번째 토큰이었다고 해서 위치 임베딩 500번을 쓰는 것이 아니다. 잘라낸 조각 안에서 첫 번째 토큰이면 0번 위치를 쓴다.

처음에는 이상하게 느껴질 수 있지만, 작은 GPT를 학습할 때는 자연스러운 방식이다. 모델은 "전체 책에서 몇 번째 글자인가"보다 "현재 문맥 조각 안에서 몇 번째 토큰인가"를 기준으로 다음 토큰을 예측한다.

위치 임베딩이 생기면 Attention이 달라진다

위치 임베딩을 더하기 전에는 같은 토큰이 어디에 나오든 같은 토큰 임베딩을 가진다. 하지만 위치 임베딩을 더한 뒤에는 달라진다.

위치 0의 '밥' = token_embedding(밥) + position_embedding(0)
위치 5의 '밥' = token_embedding(밥) + position_embedding(5)

두 벡터는 같은 토큰에서 출발했지만, 최종 입력 벡터는 다르다. 따라서 이 벡터들로부터 만들어지는 Query, Key, Value도 달라진다. 결과적으로 Attention 점수도 달라진다.

이것이 위치 임베딩의 힘이다. 위치 정보는 Transformer 바깥에서 장식처럼 붙어 있는 것이 아니다. Attention 계산 전체에 영향을 미친다. 각 토큰은 자기 의미와 자기 위치를 함께 들고 Transformer Block 안으로 들어간다.

문장을 읽는 독자처럼, 모델도 이제 "무엇이 나왔는가"뿐 아니라 "어디에 나왔는가"를 함께 볼 수 있게 된다.

작은 모델에서 관찰할 수 있는 것

뒤에서 Mini GPT를 학습시키면 위치 임베딩이 실제로 어떤 역할을 하는지 간접적으로 관찰할 수 있다. 예를 들어 위치 임베딩을 제거하고 학습하면 모델은 반복 패턴이나 순서가 중요한 구조를 훨씬 더 어색하게 배울 수 있다.

문자 단위 모델에서는 특히 순서가 중요하다. "학교"라는 단어를 문자 단위로 보면 "학" 다음에 "교"가 와야 자연스럽다. "교" 다음에 "학"이 오면 다른 의미가 된다. 문장부호도 위치와 밀접하다. 여는 따옴표가 나왔으면 뒤에 닫는 따옴표가 와야 하고, 문장이 끝날 때쯤 마침표가 나올 가능성이 커진다.

물론 작은 데이터와 작은 모델에서는 위치 임베딩의 효과가 거대한 LLM처럼 극적으로 보이지 않을 수도 있다. 하지만 원리는 같다. 토큰 임베딩만 있으면 모델은 재료를 보고, 위치 임베딩이 더해지면 재료의 배열까지 본다.

이번 장의 핵심 정리

LLM이 문장을 다루려면 토큰의 의미뿐 아니라 순서도 알아야 한다. Self-Attention은 토큰 사이의 관계를 계산하는 강력한 장치지만, 위치 정보가 없다면 토큰들이 어떤 순서로 놓였는지 알기 어렵다.

위치 임베딩은 이 문제를 해결하기 위해 등장한다. 가장 단순한 방법은 각 토큰 위치에 해당하는 벡터를 토큰 임베딩에 더하는 것이다. 그러면 Transformer는 각 토큰이 무엇인지와 어디에 있는지를 함께 입력으로 받는다.

사인·코사인 위치 인코딩은 공식으로 위치 벡터를 만들고, 학습형 위치 임베딩은 위치 벡터를 모델이 직접 학습한다. RoPE 같은 방식은 Query와 Key를 위치에 따라 회전시켜 상대적 거리 정보를 Attention 계산에 녹여 넣는다. 긴 문맥에서는 단순히 "몇 번째인가"보다 "서로 얼마나 떨어져 있는가"가 중요해지므로 이런 상대 위치 방식이 유리할 수 있다.

이 책의 Mini GPT에서는 구현이 쉽고 원리를 관찰하기 좋은 학습형 절대 위치 임베딩을 사용할 것이다. 토큰 임베딩과 위치 임베딩을 더한 뒤 Transformer Block에 넣는 구조다.

이제 Transformer의 입력에는 토큰의 의미와 순서가 모두 담겼다. 다음으로 살펴볼 것은 각 토큰이 Attention을 거친 뒤, 자기 내부 표현을 어떻게 더 풍부하게 바꾸는가이다. 그 역할을 맡는 장치가 Feed Forward Network다.

책 목록

처음 만나는 LLM: 원리부터 Mini GPT 구현, RAG 서비스 개발까지

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기