10장 — 어텐션과 트랜스포머: 멀리 떨어진 칸끼리 읽기
자주 하는 실수와 요약
자주 하는 실수
| 실수 | 나온 문제 | 바로잡는 법 |
|---|---|---|
| 점수가 가장 큰 것 하나만 읽는다고 봄 | 1 | 소프트맥스 비중으로 모두를 섞는다. 점수 차이와 배율이 쏠림을 정한다 |
| 점수를 키우면(나누지 않으면) 확실하게 골라 좋다고 봄 | 2 | 처음부터 99.97%로 쏠리면 기울기 p(1 − p)가 0.000335로 사라진다. √d_k 로 나눠 점수의 표준편차를 1로 맞춘다 |
| √d_k 로 나누면 학습 뒤의 쏠림도 막힌다고 봄 | 2 | 성분이 독립인 처음 상태에서 크기를 맞출 뿐이다. 학습이 만든 쏠림은 남는다 |
| 그림을 두 배로 키우면 점수 표도 두 배라고 셈 | 3 | 칸이 네 배, 점수 표는 칸 수의 제곱이라 열여섯 배다 |
| 소프트맥스를 질의 묶음으로 잘라 계산하면 답이 달라진다고 봄 | 3 | 소프트맥스는 한 질의의 줄 안에서만 나눈다. 줄 묶음마다 따로 해도 같다 |
| 회전 위치 인코딩에서 내적만 보고 절대 자리를 안다고 봄 | 4 | 내적에는 자리의 차이만 남는다. 빠르기 하나로는 한 바퀴 뒤 차이도 겹친다 |
| 셀프 어텐션을 쓰는 신경망은 늘 순서를 모른다고 봄 | 5, 6 | 어텐션 앞에 자리를 아는 층(합성곱)이 없을 때만이다 |
| 머리를 나누면 매개변수와 계산이 머리 수만큼 는다고 봄 | 7 | 행렬을 머리 수로 자를 뿐이라 둘 다 그대로다. 느는 것은 소프트맥스(비중 분포)의 수다 |
| 머리들의 비중 평균을 「머리 하나였을 때의 비중」으로 읽음 | 8 | 평균 낸 비중은 어떤 소프트맥스에서도 나온 값이 아니다 |
| 문장 하나의 비중 표로 머리의 역할을 정함 | 8 | 그 문장에서의 버릇일 뿐이다. 여러 문장으로 확인해야 한다 |
| 층 정규화가 토큰의 정보를 지우지 않는다고 봄 | 9 | 토큰 하나의 평균과 크기를 지운다. 앞 정규화는 그래서 줄기에는 정규화 전 값을 남긴다 |
| 앞 정규화의 뒤쪽 블록은 일을 안 한다고 봄 | 10 | 처음에는 몫이 작지만, 학습이 몫의 크기와 방향을 정한다 |
| 뒤 정규화 트랜스포머를 깊게 쌓고 예열 없이 큰 학습률로 시작함 | 11 | 블록 24벌 실험에서 손실이 ln 64 에서 내려오지 않았다. 예열을 두거나 앞 정규화를 쓴다 |
| 크로스 어텐션은 글 토큰이 하나여도 칸마다 다르게 읽는다고 봄 | 12 | 토큰이 하나거나 모두 같으면 모든 칸이 같은 벡터를 받는다 |
| 「어텐션 블록」의 매개변수는 대부분 어텐션이라고 봄 | 13 | SD 1.x 에서는 MLP 가 55.7%다 |
| 글 인코더를 바꿔도 U-Net 은 그대로 쓸 수 있다고 봄 | 13 | 크로스 어텐션의 열쇠·값 행렬은 글 쪽 폭에 맞춰져 있어 크기부터 바뀐다 |
| 적어 둔 누계·캐시는 앞 기록이 바뀌어도 쓸 수 있다고 봄 | 14, 15 | 앞의 것이 다시 바뀌지 않을 때만 맞다. 트랜스포머에서는 가림이 그 보장이다 |
| KV 캐시 크기에서 열쇠·값 두 벌을 빼먹음 | 16 | 2 × 블록 수 × 열쇠·값 머리 수 × 머리 길이 × 바이트 |
요약
합성곱은 자리마다 무게를 정해 두어 멀리 있는 칸을 읽기 어려웠다. 셀프 어텐션은 토큰마다 질의·열쇠·값을 만들고, 질의와 열쇠의 내적을 √d_k 로 나눠 소프트맥스를 씌운 비중으로 값을 섞어, 거리와 상관없이 내용이 맞는 토큰을 읽는다. 내용만 보므로 토큰을 뒤섞어도 출력이 따라 옮겨 갈 뿐이라, 자리는 위치 인코딩으로 따로 적는다. 자리 벡터를 더하는 사인 꼴에서 질의와 열쇠를 자리만큼 돌리는 회전 꼴로 넘어가면서, 내적에는 자리의 차이만 남게 되었고 그림은 행과 열을 따로 돌린다. 한 소프트맥스는 평균 하나만 내므로 질의·열쇠·값을 여러 머리로 나눠 따로 읽고 이어 붙인다. 계산량과 매개변수는 그대로다. 여러 머리 어텐션과 토큰마다의 MLP 를 「층 정규화 → 부품 → 잔차 더하기」로 감싼 트랜스포머 블록을 쌓되, 정규화를 부품 입력에 두면 깊이 쌓아도 예열 없이 학습된다. 질의를 그림 칸에서, 열쇠와 값을 글 토큰에서 만들면 크로스 어텐션이 되어 칸마다 필요한 낱말을 골라 읽는다. 한 토큰씩 이어 쓰는 모델은 뒤 토큰을 가려 앞 토큰의 열쇠와 값이 바뀌지 않게 하고, 그것을 KV 캐시에 적어 두어 걸음마다 새 토큰의 계산만 한다.
flowchart LR A["합성곱<br/>자리마다 정한 무게"] -->|"멀리 있는 짝을<br/>내용으로 못 고른다"| B["셀프 어텐션<br/>질의·열쇠로 비중, 값을 섞기"] B -->|"뒤섞어도<br/>같게 읽는다"| C["위치 인코딩<br/>사인 더하기 → 회전"] B -->|"소프트맥스 하나는<br/>평균 하나"| D["여러 머리 어텐션"] D --> E["트랜스포머 블록<br/>층 정규화 → 부품 → 잔차"] C --> E E -->|"글을 읽어야 한다"| F["크로스 어텐션<br/>질의는 그림, 열쇠·값은 글"] E -->|"한 토큰씩 이어 쓴다"| G["가림 + KV 캐시"]
막힌 곳
이제 그림의 칸들이 거리와 상관없이 서로를 읽고, 글 토큰까지 골라 읽게 할 수 있다. 그런데 셀프 어텐션의 점수 표는 칸 수의 제곱으로 자랐다. Stable Diffusion 1.x 가 512 × 512 그림을 만들 때 가장 고운 단계의 칸이 4096개였던 것은, 그림을 가로세로 8배 줄인 64 × 64칸 위에서 일했기 때문이다. 픽셀마다 칸 하나로 두면 칸이 262,144개, 점수 표 하나에 687억 개가 넘는 점수가 들어간다. 그 64 × 64칸, 칸마다 수 4개인 그림은 어디서 왔을까? 그림을 작게 줄여 놓고 그 위에서 잡음을 섞고 걷어 내도, 줄이기 전의 그림으로 되살릴 수 있을까?