쿼리, 키, 값
동일한 입력으로부터의 세 가지 선형 매핑
임베딩(활동 4) 후, 각 위치는 768차원 벡터 x_t를 운반합니다. 어텐션은 x의 세 가지 서로 다른 프로젝션을 생성하는 것으로 시작합니다:
Q (쿼리): 이 위치가 알고 싶은 것은 무엇인가?
K (키): 이 위치가 다른 위치에 제공하는 것은 무엇인가?
V (값): 주의를 받으면 이 위치가 전달하는 내용은 무엇인가?
각 투영은 학습된 가중치 행렬에서 나온다:
Q = x · W_Q # W_Q shape: (d_model, d_k)
K = x · W_K # W_K shape: (d_model, d_k)
V = x · W_V # W_V shape: (d_model, d_k)
백프로퍼게이션을 통해 훈련되는 세 개의 행렬. 모델은 학습합니다: 이 위치에서 어떤 쿼리가 유용한 과거 맥락을 가장 잘 검색할까? 이 위치의 내용을 잘 광고하는 키는 무엇일까? 선택되었을 때 어떤 값을 전달할까?
도서관 비유
도서관 카드 카탈로그를 상상해 보세요. 특정 주제(query)를 염두에 두고 들어갑니다. 모든 카드에는 키워드(key)가 나와 있습니다. 주제가 카드의 키워드와 일치하면 책 내용(value)을 가져옵니다. Attention은 모든 토큰에 대해 병렬로 이 작업을 수행합니다: 모든 위치가 다른 모든 위치를 쿼리하고, 정렬을 랭킹하며, 값 벡터의 가중 조합을 검색합니다.
ANDREA-120M 차원
| 수량 | 값 | 비고 |
|---|---|---|
| d_model | 768 | 위치마다의 벡터 크기 |
| n_head | 12 | 병렬 어텐션 헤드 |
| d_k | 64 | 헤드당 차원 (= d_model / n_head) |
| T | 1024 | 컨텍스트 길이 |
d_k = d_model / n_head = 768 / 12 = 64. 각 헤드는 전체 768차원 공간의 64차원 부분을 봅니다. Activity 6 (grow_a_language_model_multi_head)에서 헤드당 분할을 자세히 다룹니다.
d_k 계산하기
왜 sqrt(d_k)로 나누는가
점수 행렬
Q와 K가 존재하면(각각 shape (T, d_k)), 어텐션은 점수 행렬을 계산합니다:
scores = Q · K^T # shape: (T, T)
scores[i, j] = 위치 i의 쿼리가 위치 j의 키와 얼마나 강하게 정렬되는지. 모든 (i, j) 쌍마다 하나의 점수: 1024 × 1024 = 1,048,576 scores per attention head per forward pass.
왜 나누는가
두 개의 무작위 d차원 단위 벡터의 내적은 크기가 sqrt(d) 정도입니다. 스케일링 없이, scores는 d_k와 함께 증가합니다:
- d_k = 64: 일반적인 내적 값은 8 정도의 크기.
- d_k = 256: 일반적인 내적 값은 16 정도의 크기.
- d_k = 4096: 일반적인 내적 값은 64 정도의 크기.
큰 점수들은 뾰족한 softmax를 생성합니다 (한 위치가 지배하고, 다른 곳에서 그래디언트가 사라짐). 학습이 멈춥니다. 스케일링이 크기를 수정합니다:
scaled_scores = (Q · K^T) / sqrt(d_k)
ANDREA-120M의 경우, sqrt(d_k) = sqrt(64) = 8입니다. 모든 점수가 8로 나누어집니다. d_k에 관계없이 크기가 대략 단위 스케일로 유지됩니다. Softmax가 잘 작동합니다. 그래디언트가 흐릅니다.
Vaswani의 원래 정당화
Attention Is All You Need (2017)에서: 'd_k의 값이 클 때, 내적의 크기가 커져 softmax 함수를 극히 작은 그래디언트가 있는 영역으로 밀어붙입니다.' sqrt(d_k) 나눗셈이 그 성장을 상쇄합니다.
코드 뷰
microgpt_cuda.cu 내부에서 이 스케일링은 리터럴 나눗셈으로 나타납니다:
scores[i][j] = dot(Q[i], K[j]) * (1.0f / sqrtf(d_k));
각 점수당 하나의 부동소수점 곱셈. 저렴함. 중요함.
d_model = 4096에서의 스케일링
위치 i가 위치 j > i를 볼 수 없는 이유
생성에서 비롯된 제약
ANDREA는 한 번에 하나의 토큰을 생성합니다. 추론 시, 위치 0에서 첫 번째 토큰을 생성한 후, 위치 1에서 위치 0의 출력을 보고 두 번째 토큰을 생성하며, 이런 식으로 진행됩니다. 모델은 생성 중에 미래 토큰에 절대 접근할 수 없습니다.
훈련도 이를 반영해야 합니다. 훈련 중 위치 5가 위치 6에 주의를 기울일 수 있다면, 모델은 단축경로를 학습하게 됩니다: '토큰 6을 토큰 6을 읽음으로써 예측하라'. 추론 시 이 단축경로는 사라집니다(토큰 6이 아직 존재하지 않음). 모델의 훈련 대 추론 행동이 치명적으로 분기될 것입니다.
마스크
인과적 마스크는 위치 i에서 위치 j > i로의 주의를 차단합니다. 구현: j > i인 모든 곳에서 scaled_scores[i][j] = -infinity로 설정. 소프트맥스 후 해당 항목들은 exp(-inf) = 0이 됩니다. 마스크는 미래 위치에 대한 주의를 깔끔하게 0으로 만듭니다.
for i in range(T):
for j in range(T):
if j > i:
scaled_scores[i][j] = -1e9 # 사실상 -inf
소프트맥스(행별) 적용 후, 각 행의 합은 1이 되지만, [0, i] 항목만 확률 질량을 가집니다. 위치 i는 과거 위치의 정보만 혼합합니다.
마스크 시각화
마스크가 적용된 점수 행렬의 형태 (T, T)는 하삼각 구조처럼 보입니다:
마스크 적용 후 scaled_scores, 행별 softmax:
행 0: [1.0, 0, 0, 0, ...] # 자신만 봄
행 1: [0.4, 0.6, 0, 0, ...] # 위치 0, 1을 봄
2행: [0.2, 0.3, 0.5, 0, ...] # 0, 1, 2를 봄
3행: [0.1, 0.2, 0.3, 0.4, ...] # 0, 1, 2, 3을 봄
...
행당 엄격한 하삼각 확률 분포. 미래는 보이지 않음.
왜 Decoder-Only Transformer가 이것을 필요로 하는가
ANDREA, GPT, LLaMA와 같은 디코더 전용 모델들은 모두 하나의 목표를 공유합니다: 과거로부터 다음 토큰을 예측하는 것. 인과 마스크는 이 목표를 병렬로 훈련 가능하게 만듭니다: 모든 위치가 동시에 자신의 다음 토큰 예측을 계산하며, 어떤 위치도 앞을 들여다보는 치트 없이요.
마스크 & 맛
점수에서 출력으로
소프트맥스: 점수에서 확률로
마스킹되고 스케일링된 점수는 여전히 실수 범위에 있습니다. 소프트맥스는 각 행을 확률 분포로 변환합니다:
A[i][j] = exp(scaled_scores[i][j]) / sum_k exp(scaled_scores[i][k])
세 가지 속성이 결과로 나타납니다:
- 모든 (i, j)에 대해 A[i][j] >= 0.
- 모든 행 i에 대해 sum_j A[i][j] = 1.
- 더 큰 원시 점수는 더 큰 확률을 생성 (단조 증가).
행 i의 확률 벡터는 모델에게 알려줍니다: 출력 계산 시 위치 i가 각 이전 위치에 얼마나 주의를 기울여야 하는가?
가중 V 합계
위치 i에 대한 최종 어텐션 출력:
output[i] = sum_j A[i][j] · V[j]
각 값 벡터 V[j]는 어텐션 확률 A[i][j]로 가중되고, 그 후 합산됩니다. 위치 i의 출력은 모든 이전 위치의 값 벡터를 관련성에 따라 가중하여 결합한 것입니다.
행렬 형태로, 한 번에 모든 위치:
Attention(Q, K, V) = softmax(mask(Q · K^T / sqrt(d_k))) · V
한 줄. 전체 어텐션 메커니즘. Vaswani 등은 2017년에 그 줄을 썼습니다; 트랜스포머는 그 이후로 근본적으로 변하지 않았습니다.
헤드당 출력 형태
하나의 어텐션 헤드 출력: 형태 (T, d_k). ANDREA-120M의 경우: (1024, 64). 12개의 헤드가 병렬로 계산; 출력이 (1024, 768)으로 연결되어 최종 선형 투영(W_O)으로 전달된 후, 트랜스포머 블록의 MLP로 이어짐.
Activity 6 (grow_a_language_model_multi_head)는 멀티헤드 분할을 다룸. Activity 7 (grow_a_language_model_transformer_block)은 어텐션 주변의 모든 것(잔차 연결, 레이어 정규화, MLP)을 다룸.