STEP 04 / 18

토큰마다 혼자 생각하기: Feed-Forward

어텐션이 토큰끼리 정보를 주고받는 단계라면, 피드포워드(FFN)는 받은 정보를 토큰마다 혼자 소화하는 단계다.

$$\mathrm{FFN}(x)=\max(0, xW_1 + b_1) W_2 + b_2$$

512차원을 2048차원으로 넓혔다가(ReLU) 다시 512로 줄인다. 모든 위치에 같은 가중치를 쓰지만, 위치끼리는 섞지 않는다. 그래서 이름이 "position-wise"다.

코드는 nn.Linear 두 개와 ReLU, dropout이 전부다.

실행 결과에서 볼 것

  • 문장 전체를 넣었을 때 4번째 토큰의 출력과 그 토큰 하나만 넣었을 때의 출력이 같다. 토큰 사이에 정보가 오가지 않는다는 뜻이다.
  • 파라미터가 약 210만 개로, 어텐션 블록(105만 개)의 두 배다. Transformer 파라미터의 상당 부분이 FFN에 있다.

코드 변화

+13 −0 · 파일 전체 86줄

이전 단계의 transformer.py와 비교했다. 초록 줄이 새로 생긴 줄, 빨간 줄이 사라진 줄이다.

GitHub에서 보기 ↗
7171 del key
7272 del value
7373 return self.linears[-1](x)
74+
75+
76+class PositionwiseFeedForward(nn.Module):
77+ "Implements FFN equation."
78+
79+ def __init__(self, d_model, d_ff, dropout=0.1):
80+ super(PositionwiseFeedForward, self).__init__()
81+ self.w_1 = nn.Linear(d_model, d_ff)
82+ self.w_2 = nn.Linear(d_ff, d_model)
83+ self.dropout = nn.Dropout(dropout)
84+
85+ def forward(self, x):
86+ return self.w_2(self.dropout(self.w_1(x).relu()))

실행 결과

실행한 코드 demo.py 보기
1import torch
2
3from transformer import PositionwiseFeedForward
4
5torch.manual_seed(0)
6
7ffn = PositionwiseFeedForward(d_model=512, d_ff=2048, dropout=0.0)
8x = torch.randn(2, 10, 512)
9print("입력:", tuple(x.shape), "→ w_1:", tuple(ffn.w_1(x).shape), "→ 출력:", tuple(ffn(x).shape))
10n = sum(p.numel() for p in ffn.parameters())
11print(f"파라미터 수: {n:,} (512×2048+2048) + (2048×512+512)")
12print()
13
14print("'위치별(position-wise)' = 토큰마다 따로, 같은 가중치로 계산한다")
15alone = ffn(x[:, 3:4]) # 4번째 토큰만 넣어도
16print(" 전체 문장에서 4번째 토큰 출력과 혼자 넣었을 때 출력의 차이:",
17 f"{(ffn(x)[:, 3:4] - alone).abs().max().item():.1e}")
18print(" → 토큰끼리 정보를 섞는 건 어텐션뿐이고, FFN은 각 토큰을 혼자 변환한다")
19print()
20
21h = ffn.w_1(x).relu()
22print(f"ReLU 뒤 0이 된 비율: {(h == 0).float().mean().item():.0%}")
입력: (2, 10, 512) → w_1: (2, 10, 2048) → 출력: (2, 10, 512)
파라미터 수: 2,099,712  (512×2048+2048) + (2048×512+512)

'위치별(position-wise)' = 토큰마다 따로, 같은 가중치로 계산한다
  전체 문장에서 4번째 토큰 출력과 혼자 넣었을 때 출력의 차이: 5.4e-07
  → 토큰끼리 정보를 섞는 건 어텐션뿐이고, FFN은 각 토큰을 혼자 변환한다

ReLU 뒤 0이 된 비율: 50%