LLM 엔지니어링3장 · Attention 과 Transformer, GPT 직접 구현8 / 23← 이전목차다음 →MetaContents

3장. Attention 과 Transformer, GPT 직접 구현

마스크: 앞만 보기

받아쓰기 시험을 볼 때 답안지의 뒷부분이 보이면 시험이 되지 않습니다. GPT 는 다음 글자를 맞히며 배우는데, 뒤의 글자를 볼 수 있다면 답을 보고 맞히는 셈입니다. 그래서 뒤의 글자를 가립니다. 이 가림표가 마스크입니다.

고객 콜론 빈칸 이불 여섯 글자에서 각 글자가 자기와 앞의 글자만 볼 수 있음을 보이는 삼각형 표 그림

줄이 지금 글자, 칸이 보는 대상입니다. 청록 "봄" 칸은 볼 수 있는 자리, 회색 "가림" 칸은 가린 자리입니다. 첫 글자 "고"는 자기 자신만 보고, 마지막 "불"은 앞의 다섯 글자를 모두 봅니다. 왼쪽 아래 삼각형만 남기 때문에 인과적(앞에서 뒤로만 이어지는) 마스크라고 부릅니다. 여기서 답안지의 뒷부분이 가린 칸입니다.

가리는 법

가릴 칸의 점수를 아주 작은 수(음의 무한대)로 바꾸면, softmax 를 지난 뒤 그 칸의 비율이 0 이 됩니다.

단계하는 일
1torch.tril(torch.ones(T, T)) 로 아래 삼각형이 1 인 표를 만든다
2표가 0 인 칸의 점수를 -inf 로 바꾼다
3softmax 를 하면 그 칸은 0, 남은 칸끼리 합이 1
Python
import torch

T = 4
scores = torch.zeros(T, T)                         # 점수가 모두 같다고 해 보자
mask = torch.tril(torch.ones(T, T))                # 아래 삼각형만 1
scores = scores.masked_fill(mask == 0, float("-inf"))   # 뒤쪽 칸은 -무한대로
print(torch.softmax(scores, dim=-1))

첫 줄은 [1, 0, 0, 0], 둘째 줄은 [0.5, 0.5, 0, 0], 넷째 줄은 [0.25, 0.25, 0.25, 0.25] 입니다. 점수가 모두 같으니 볼 수 있는 칸끼리 똑같이 나눠 가졌고, 뒤쪽 칸은 모두 0 입니다.

왜 1, 1/2, 1/3, 1/4 가 나오나

점수를 모두 0 으로 똑같이 두었기 때문에, 줄마다 볼 수 있는 칸끼리 1 을 똑같이 나눠 가진 것입니다. 네 걸음으로 따라가 봅니다.

1) 처음 점수는 모두 0 입니다. torch.zeros(4, 4) 로 16칸을 모두 0 으로 두었습니다. "어느 글자든 똑같이 중요하다"고 일부러 정한 것입니다.

2) 마스크를 씌우면 뒤쪽 칸만 -inf 가 됩니다. tril 은 대각선과 그 아래만 1 로 남기고, mask == 0 인 칸, 곧 대각선 위쪽이 -inf(음의 무한대)로 바뀝니다.

지금 글자1번 칸2번 칸3번 칸4번 칸
1번 줄0-inf-inf-inf
2번 줄00-inf-inf
3번 줄000-inf
4번 줄0000

3) softmax 의 세 걸음을 거칩니다. "Attention 의 원리" 절에서 본 대로 exp 로 바꾸고, 줄마다 더하고, 합으로 나눕니다. exp(0) 은 1 이고, exp(-inf) 는 0 입니다. e 를 음의 무한대만큼 곱하면 한없이 0 에 가까워지기 때문입니다.

줄exp 를 지난 값합합으로 나눈 결과
1번 줄1, 0, 0, 011, 0, 0, 0
2번 줄1, 1, 0, 020.5, 0.5, 0, 0
3번 줄1, 1, 1, 030.3333, 0.3333, 0.3333, 0
4번 줄1, 1, 1, 140.25, 0.25, 0.25, 0.25

4) 뜻으로 읽습니다.

  • 1번 글자는 자기 자신만 볼 수 있어서 자기를 100% 참고합니다.
  • 2번 글자는 두 글자를 볼 수 있고, 점수가 같으니 반씩 나눕니다. 3번은 셋으로, 4번은 넷으로 똑같이 나눕니다.
  • 가린 칸은 exp 에서 0 이 되므로 합에도 들어가지 않고, 비율도 정확히 0 입니다. 뒤 글자를 전혀 참고하지 않는다는 뜻입니다.

실제 모델에서는 점수가 칸마다 달라서 똑같이 나뉘지 않습니다. 이 예는 마스크가 하는 일만 보이려고 점수를 같게 둔 것입니다. 그래도 "가린 칸은 0, 남은 칸끼리 합이 1"이라는 점은 실제 모델에서도 같습니다.

dim=-1 은 왜 쓰나

dim 은 어느 방향으로 묶어 softmax 를 할지 정합니다. -1 은 파이썬 목록의 a[-1] 처럼 마지막 축을 뜻하고, (4, 4) 표에서는 dim=1 과 같습니다.

dim 값묶는 방향합이 1 이 되는 곳
0세로(한 칸 안의 네 줄)칸마다
1 또는 -1가로(한 줄 안의 네 칸)줄마다
  • 줄은 지금 글자이고, 한 글자가 볼 수 있는 글자들에게 참고 비율 100% 를 나눠 주어야 하므로 줄마다 합이 1 이어야 합니다.
  • dim=0 으로 바꾸면 세로로 합이 1 이 됩니다. 1번 줄의 합이 0.25, 4번 줄의 합이 2 를 넘어 비율로 읽을 수 없는 표가 됩니다.
  • 1 대신 -1 로 쓰는 까닭은, 실제 모델에서는 문장 여러 개와 멀티헤드 때문에 표가 (문장 수, 헤드 수, 4, 4) 처럼 커져 칸 방향 축의 번호가 바뀌기 때문입니다. -1 은 모양이 바뀌어도 늘 마지막 축, 곧 칸 방향을 가리킵니다.

코드에 나오는 함수

코드쉬운 뜻
torch.zeros(T, T)모든 칸이 0인 (4, 4) 표를 만든다. 점수가 모두 같은 경우를 흉내 낸다
torch.ones(T, T)모든 칸이 1인 (4, 4) 표를 만든다
torch.tril(...)"lower triangle"의 줄임말이다. 대각선과 그 아래만 남기고 위쪽 칸을 0으로 만든다
mask == 0칸마다 0인지 견줘 True, False 표를 만든다. True 인 칸이 가릴 칸이다
scores.masked_fill(조건, 값)조건이 True 인 칸만 값으로 바꾼 새 텐서를 돌려준다. 원래 scores 는 바뀌지 않아서 다시 담는다
float("-inf")음의 무한대다. softmax 를 지나면 이 칸의 비율은 정확히 0이 된다

masked_fill 은 마스크가 True 인 칸을 채웁니다. 그래서 "볼 칸이 1"인 tril 표를 그대로 넣지 않고, mask == 0 으로 뒤집어 "가릴 칸이 True"인 표를 넣습니다.

주의할 점

  • 마스크는 학습 때 한꺼번에 계산하려고 필요합니다. 문장 하나로 "1글자 보고 2번째 맞히기", "2글자 보고 3번째 맞히기"를 동시에 연습할 수 있게 됩니다.
  • 번역 모델의 인코더나 BERT 처럼 문장 전체를 읽는 모델은 이 마스크를 쓰지 않습니다.
← 이전 절3장 · 셀프 어텐션 계산다음 절 →3장 · 멀티헤드와 위치 정보