트랜스포머 쉽게 이해하기 (2) - 디코더

2026. 4. 20. 19:38·개발지식/AI

들어가며


[이전 글]에서는 영어 문장 "I am studying"이 인코더를 거치면서 어떻게 문맥이 녹아든 벡터로 변환되는지 살펴봤습니다.

이번 글에서는 그 벡터를 받아 불어(혹은 독일어) 문장을 한 토큰씩 만들어내는 디코더를 다룹니다.

 

예시 문장은 다음과 같이 가정하겠습니다.

  • 원문(영어): I am studying Transformer
  • 번역(독일어): Ich studiere Transformer

그리고 지금까지 디코더가 "Ich", "studiere"까지 생성했고, 이제 그 다음 단어 "Transformer"를 예측하려는 상황을 떠올려봅시다.



디코더 블록의 전체 구조


인코더 블록이 3단계(멀티헤드 셀프 어텐션 → FFN → 잔차 연결/층 정규화)였다면, 디코더 블록은 한 단계가 더 있습니다.

입력(독일어 임베딩 + 위치 벡터)
  ↓
① 마스크드 멀티헤드 셀프 어텐션  (Q=K=V, 모두 독일어)
  ↓  잔차 연결 + 층 정규화
② 인코더-디코더 어텐션 (크로스 어텐션)
     Q = ①의 출력 (독일어)
     K, V = 인코더 최종 출력 (영어)
  ↓  잔차 연결 + 층 정규화
③ 앞먹임 신경망 (FFN)
  ↓  잔차 연결 + 층 정규화
출력(512차원)

이 블록을 N=6번 쌓으면 디코더 하나가 완성됩니다.

인코더와 다른 점은 멀티헤드 어텐션에 "마스크드"가 붙다는 점과 인코더-디코더 어텐션에서 인코더의 출력을 K, V로 받아온다는 점입니다.

 

1단계: 마스크드 멀티헤드 셀프 어텐션


디코더는 번역을 왼쪽에서 오른쪽으로 한 단어씩 만들어냅니다. "Ich"를 만들 때는 "studiere"나 "Transformer"가 아직 존재하지 않아야 하고, "studiere"를 만들 때는 "Transformer"를 아직 몰라야 합니다.

그런데 훈련할 때는 정답 문장 "Ich studiere Transformer"를 한꺼번에 디코더에 집어넣습니다(이래야 학습이 빠릅니다). 이렇게 한꺼번에 넣으면 셀프 어텐션이 미래 단어까지 참고해서 현재 단어를 예측해 버립니다. 즉 "studiere" 위치에서 "Transformer"를 미리 보고 다음 단어를 맞추는 상황이 생깁니다.

 

마스크는 "어디서" 일어나는가? — 가장 헷갈리는 부분


많은 설명이 "미래 토큰을 마스킹한다"고만 말해서 마치 입력에서 토큰을 빼는 것처럼 느껴지는데 "Ich studiere Transformer" 세 토큰이 디코더에 모두 들어갑니다. 각 토큰은 자기 몫의 Q, K, V 벡터를 만듭니다. 그 다음 Q와 K를 곱하면 3×3 어텐션 스코어 행렬이 나오는데, 바로 이 행렬의 오른쪽 위 삼각형을 `-∞`로 덮어버리는 것이 마스킹입니다.

토큰이 사라지는 게 아니라, 어텐션 가중치만 0으로 만듭니다. 그래서 입력 토큰 수는 항상 3개(또는 문장 길이만큼)이고 출력 벡터도 항상 토큰 수와 동일한 개수만큼 나옵니다.

여기서 왜 -∞로 덮는지가 궁금할 수 있는데, 어텐션 스코어는 마지막에 소프트맥스를 거칩니다. 소프트맥스는 exp(-∞) = 0이므로, -∞로 마스킹된 자리는 최종 어텐션 가중치에서 정확히 0이 됩니다.

 

마스킹 이후 각 토큰 벡터에 담기는 정보

마스킹을 거친 후 각 토큰 위치의 출력 벡터가 볼 수 있는 정보는 다음과 같이 달라집니다.

 

2단계: 인코더-디코더 어텐션 (크로스 어텐션)

 


마스크드 셀프 어텐션이 독일어 문장 내부의 관계를 본다면, 크로스 어텐션은 독일어 ↔ 영어의 관계를 봅니다.

  • Q: 마스크드 어텐션의 출력 (독일어에서 옴)
  • K, V: 인코더 최종 출력 (영어에서 옴)

여기서 헷갈릴 수 있는 부분이 인코더 최종 출력 하나를 디코더의 6개 블록이 전부 공유해서 K, V로 쓴다는 것입니다. 인코더는 딱 한 번만 돌고 그 최종 출력을 디코더 6개 블록이 똑같이 참조합니다.

 

3단계: 앞먹임 신경망(FFN) + 잔차 연결 + 층 정규화


인코더와 동일합니다. 잔차 연결과 층 정규화를 다시 거칩니다.

여기까지가 디코더 블록 하나입니다. 이걸 6번 반복하면 디코더가 완성됩니다.

 

4단계: 선형 층(Linear)과 소프트맥스 — 다음 단어 예측

 


디코더 6개 블록을 다 통과한 결과물은 토큰 개수만큼의 512차원 벡터 (512차원이라는 건 처음 토큰 1개를 몇 차원의 벡터로 정의할지 정하는 과정에서 정해지게 됩니다)입니다. "Ich studiere"를 넣었다면 512차원 벡터 2개가, "Ich studiere Transformer"를 넣었다면 3개가 나옵니다.

그런데 실제로 다음 단어를 고르려면, 이 512차원 벡터를 어휘 사전 전체(약 3만 개)의 확률 분포로 변환해야 합니다. 이 역할을 선형 층이 맡습니다.

512차원 벡터  ──(512 × 30,000 가중치 행렬)──▶  30,000차원 벡터
                                                 │
                                                 ▼
                                            소프트맥스
                                                 │
                                                 ▼
                                      어휘 사전 각 단어의 확률
                                      (가장 큰 값이 다음 단어)

가장 헷갈리는 질문: 어느 위치의 벡터를 선형 층에 넣어야 하지?

 

지금까지 "Ich", "studiere"를 만들었고, 그 다음인 "Transformer"를 예측하고 싶다고 합시다. 디코더의 출력 벡터는 2개입니다.

  • 벡터 A: "Ich" 위치의 출력 (512차원)
  • 벡터 B: "studiere" 위치의 출력 (512차원)

 

둘 중 어떤 걸 선형 층에 넣어야 할까요? 정답은 B(= 마지막 위치)의 벡터입니다. 왜일까요?

  • 벡터 A ("Ich" 위치) = [Ich] 정보만 담고 있음
  • 벡터 B ("studiere" 위치) = [Ich, studiere] 정보를 담고 있음

 

벡터 A에는 "studiere"가 이미 생성되었다는 사실조차 들어있지 않습니다. 벡터 A를 선형 층에 넣으면 모델은 "Ich 다음에 올 단어는?"이라는 질문에 답하게 되고 그 답은 바로 "studiere"가 될 가능성이 높습니다. 이미 우리가 아는 단어죠.

반면 벡터 B는 "Ich studiere까지 봤을 때, 그 다음에 올 단어는?"이라는 질문에 해당합니다. 그 답이 우리가 원하는 Transformer입니다.

디코더의 각 위치 출력 벡터는 그 위치까지의 문맥을 보고 그 다음 단어를 예측하는 용도로 만들어집니다. 그래서 "n번째 다음 단어"를 예측하려면 "n번째 위치의 출력 벡터" 를 선형 층에 넣는 것이고, 현재 시점까지 생성된 마지막 단어의 위치가 곧 "다음 단어를 예측할 자리"입니다.

 

훈련할 때는 벡터 A도 버리지 않고 "studiere"를 맞추는 손실 함수에 사용되지만(모든 위치가 각자 다음 단어를 예측하도록 병렬 학습) 추론 시 새 단어를 뽑을 때는 마지막 위치의 벡터만 사용합니다.

 

전체 흐름 한 번에 보기

 


지금까지 본 디코더 블록 구조 → 마스크드 셀프 어텐션 → 크로스 어텐션 → FFN → 선형 층 → 소프트맥스 → 다음 단어 선택의 흐름을, 인코더와 합쳐서 한 장으로 정리하면 다음과 같습니다.

이렇게 뽑힌 다음 단어를 다시 디코더 입력 맨 뒤에 붙여 넣고, 같은 과정을 반복합니다. 문장 종결 토큰(<EOS>)이 나올 때까지 이 과정이 이어지면서 번역 문장이 완성됩니다.

 

정리


  • 디코더 블록 = 마스크드 셀프 어텐션 + 크로스 어텐션 + FFN, 각 단계 뒤에 잔차 연결과 층 정규화
  • 마스킹은 토큰을 빼는 게 아니라 어텐션 스코어 행렬의 오른쪽 위 삼각형을 -∞로 만드는 것. 토큰은 전부 들어가고 출력도 전부 나오지만 각 위치가 볼 수 있는 정보가 자기 자신과 그 이전 위치로 제한됨.
  • 크로스 어텐션에서는 Q는 디코더, K·V는 인코더에서 옴. 인코더는 한 번만 돌고 그 최종 출력을 디코더 6개 블록이 모두 공유.
  • 선형 층에 넣는 벡터는 항상 마지막 위치의 벡터. 마스킹 구조상 n번째 위치는 n번째까지 보고 n+1번째를 예측하는 용도로 만들어졌기 때문.



반응형
저작자표시 비영리 변경금지 (새창열림)

'개발지식 > AI' 카테고리의 다른 글

LLM 서빙 prefill·decode 분리 (1) - 분리의 이유  (0) 2026.06.11
LLM 서빙의 메모리 문제와 PagedAttention (2) - PagedAttention과 vLLM  (0) 2026.05.12
LLM 서빙의 메모리 문제와 PagedAttention (1) - KV 캐시와 단편화  (0) 2026.05.12
GPU는 어떻게 트랜스포머의 행렬 연산을 가속하는가  (0) 2026.04.17
트랜스포머 쉽게 이해하기 (1) - 인코더  (0) 2026.04.15
'개발지식/AI' 카테고리의 다른 글
  • LLM 서빙의 메모리 문제와 PagedAttention (2) - PagedAttention과 vLLM
  • LLM 서빙의 메모리 문제와 PagedAttention (1) - KV 캐시와 단편화
  • GPU는 어떻게 트랜스포머의 행렬 연산을 가속하는가
  • 트랜스포머 쉽게 이해하기 (1) - 인코더
반달bear
반달bear
꾸준히 성실하게 걷고 싶습니다. 지속 가능한 열정을 추구합니다.
  • 반달bear
    반달곰의 개발이야기
    반달bear
  • 전체
    오늘
    어제
    • 분류 전체보기 (107)
      • 개발지식 (35)
        • Spring (2)
        • Java (2)
        • DB (1)
        • Design Pattern (8)
        • CS (1)
        • Ops (12)
        • AI (8)
      • 기타 (72)
        • 일기 (5)
        • 지식 (67)
      • 일기장 (0)
  • 블로그 메뉴

    • 홈
  • 링크

    • 반달곰 Gihub
  • 공지사항

  • 인기 글

  • 태그

    소회
    실험
    java
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
반달bear
트랜스포머 쉽게 이해하기 (2) - 디코더
상단으로

티스토리툴바