🤖 AI/ML

Feed Forward Network

Feed Forward Network

순방향 연결만 있는 신경망. Transformer의 각 블록에 포함.

📖 상세 설명

Feed Forward Network(FFN, 순방향 신경망)는 입력에서 출력으로 한 방향으로만 신호가 흐르는 신경망입니다. 순환(recurrent) 연결이 없어 구조가 단순하고 학습이 안정적입니다. Transformer 아키텍처에서 각 블록마다 Self-Attention 다음에 FFN이 배치되어 비선형 변환을 수행합니다.

FFN의 역사는 1958년 Frank Rosenblatt의 퍼셉트론까지 거슬러 올라갑니다. 다층 퍼셉트론(MLP)이 발전하여 현대 딥러닝의 기초가 되었습니다. 2017년 "Attention is All You Need" 논문에서 Transformer가 제안되면서, FFN은 어텐션과 함께 핵심 구성 요소로 자리 잡았습니다.

Transformer의 FFN은 두 개의 선형 변환과 활성화 함수로 구성됩니다. 수식으로는 FFN(x) = max(0, xW1 + b1)W2 + b2로 표현되며, 원래는 ReLU를 사용했습니다. GPT 계열은 GELU, LLaMA는 SwiGLU 활성화 함수를 사용합니다. Hidden dimension은 보통 입력 차원의 4배로 설정합니다(d_model=768이면 d_ff=3072).

실무에서 FFN은 Transformer 파라미터의 약 2/3를 차지하여 모델 크기와 계산 비용에 큰 영향을 미칩니다. Mixture of Experts(MoE)는 FFN을 여러 개의 "전문가"로 분리하고 조건부로 활성화하여 효율성을 높이는 기법입니다. GPT-4, Mixtral 등이 MoE를 사용하여 파라미터 대비 계산량을 줄였습니다.

💻 코드 예제

import torch
import torch.nn as nn
import torch.nn.functional as F

# 1. 기본 Feed Forward Network (Transformer 스타일)
class FeedForward(nn.Module):
    def __init__(self, d_model=512, d_ff=2048, dropout=0.1):
        super().__init__()
        # 두 개의 선형 변환
        self.linear1 = nn.Linear(d_model, d_ff)
        self.linear2 = nn.Linear(d_ff, d_model)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        # ReLU 활성화 (원본 Transformer)
        x = self.linear1(x)
        x = F.relu(x)
        x = self.dropout(x)
        x = self.linear2(x)
        return x

# 2. GELU 활성화 (GPT 스타일)
class GPTFeedForward(nn.Module):
    def __init__(self, d_model=768, d_ff=3072, dropout=0.1):
        super().__init__()
        self.c_fc = nn.Linear(d_model, d_ff)
        self.c_proj = nn.Linear(d_ff, d_model)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        x = self.c_fc(x)
        x = F.gelu(x)  # GPT는 GELU 사용
        x = self.c_proj(x)
        x = self.dropout(x)
        return x

# 3. SwiGLU 활성화 (LLaMA 스타일) - 더 효율적
class SwiGLUFeedForward(nn.Module):
    def __init__(self, d_model=512, d_ff=1376):  # d_ff는 보통 2/3 * 4 * d_model
        super().__init__()
        self.w1 = nn.Linear(d_model, d_ff, bias=False)
        self.w2 = nn.Linear(d_ff, d_model, bias=False)
        self.w3 = nn.Linear(d_model, d_ff, bias=False)  # Gate

    def forward(self, x):
        # SwiGLU: Swish(x * W1) * (x * W3) * W2
        return self.w2(F.silu(self.w1(x)) * self.w3(x))

# 4. Transformer Block에서 FFN 사용
class TransformerBlock(nn.Module):
    def __init__(self, d_model=512, n_heads=8, d_ff=2048, dropout=0.1):
        super().__init__()
        self.attention = nn.MultiheadAttention(d_model, n_heads, dropout=dropout)
        self.ffn = FeedForward(d_model, d_ff, dropout)
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        # Self-Attention with residual
        attn_out, _ = self.attention(x, x, x)
        x = self.norm1(x + self.dropout(attn_out))

        # FFN with residual
        ffn_out = self.ffn(x)
        x = self.norm2(x + self.dropout(ffn_out))
        return x

# 사용 예시
batch_size, seq_len, d_model = 2, 10, 512
x = torch.randn(batch_size, seq_len, d_model)

ffn = FeedForward(d_model=512, d_ff=2048)
output = ffn(x)
print(f"입력: {x.shape}, 출력: {output.shape}")

# 파라미터 수 계산
total_params = sum(p.numel() for p in ffn.parameters())
print(f"FFN 파라미터 수: {total_params:,}")  # 512*2048 + 2048 + 2048*512 + 512

🗣️ 실무에서 이렇게 말하세요

모델 아키텍처 설계 회의에서

"FFN의 hidden dimension을 d_model의 4배인 3072로 설정했는데, 메모리가 부족해서 2048로 줄일까 합니다. 성능 저하가 걱정되면 SwiGLU로 바꾸는 게 나을 수도 있어요. LLaMA 논문 보면 SwiGLU가 같은 파라미터 수에서 성능이 더 좋거든요."

LLM 최적화 논의 중

"모델 파라미터의 66%가 FFN에 있습니다. MoE로 바꾸면 활성 파라미터는 유지하면서 총 파라미터를 8배로 늘릴 수 있어요. 추론 속도는 거의 그대로고 성능만 올라갑니다. Mixtral이 이 방식으로 GPT-3.5급 성능을 달성했죠."

딥러닝 기술 면접에서

"Transformer에서 FFN의 역할은 어텐션이 수집한 정보를 비선형 변환하는 것입니다. 어텐션은 토큰 간 관계를 학습하고, FFN은 각 토큰의 representation을 독립적으로 처리합니다. 최근 연구에 따르면 FFN이 일종의 key-value 메모리처럼 동작하여 지식을 저장한다고 합니다."

⚠️ 흔한 실수 & 주의사항

1
Hidden dimension 설정

FFN의 hidden dimension(d_ff)은 보통 d_model의 4배로 설정합니다. 너무 작으면 표현력이 부족하고, 너무 크면 메모리와 계산 비용이 급증합니다. SwiGLU 사용 시에는 게이트 레이어가 추가되므로 d_ff를 (2/3) * 4 * d_model로 조정해야 파라미터 수가 비슷해집니다.

2
활성화 함수 선택

ReLU는 단순하지만 음수 입력에서 gradient가 0이 되는 dying ReLU 문제가 있습니다. GELU는 GPT에서 사용되어 검증되었고, SwiGLU는 LLaMA에서 더 나은 성능을 보였습니다. 새 모델 설계 시 SwiGLU를 먼저 시도해보세요.

3
Bias 사용 여부

최신 LLM(LLaMA, Mistral 등)은 FFN에서 bias를 제거하는 추세입니다. Bias 없이도 성능 차이가 거의 없고, 파라미터 수와 계산량이 줄어듭니다. 대규모 모델에서는 이 절약이 상당할 수 있습니다.

🔗 관련 용어

📚 더 배우기