- Authors
- Name

개요
대규모 언어 모델의 파라미터 수가 수조 단위로 확장되면서, 모든 파라미터를 매 토큰마다 활성화하는 Dense 모델의 한계가 분명해졌다. 학습과 추론 비용이 파라미터 수에 비례해 증가하기 때문이다. Mixture of Experts(MoE)는 이 문제에 대한 가장 유력한 해법으로 자리 잡았다. MoE는 전체 파라미터 중 일부 전문가(Expert)만 선택적으로 활성화하여, 모델 용량은 크게 유지하면서 실제 연산량은 Dense 모델 대비 극적으로 줄인다.
2022년 Switch Transformer가 단일 전문가 라우팅으로 조 단위 파라미터 확장의 가능성을 열었고, GShard는 600B 파라미터 모델의 분산 학습 파이프라인을 제시했다. ST-MoE는 학습 안정성과 전이 학습 품질을 동시에 끌어올렸으며, Mixtral 8x7B는 오픈소스 MoE 모델의 실용성을 증명했다. DeepSeek-MoE와 DeepSeek-V3는 세분화된 전문가 분할과 보조 손실 없는 로드 밸런싱이라는 새로운 방향을 열었다. 이 글에서는 각 논문의 핵심 기여를 분석하고, 라우팅 메커니즘, 학습 안정성, 추론 최적화까지 MoE를 실전에서 운영하기 위한 전략을 정리한다.
MoE 핵심 개념
Sparse Activation의 원리
MoE의 핵심 아이디어는 조건부 연산(Conditional Computation)이다. 입력 토큰마다 전체 전문가 중 일부만 활성화하므로, 모델의 총 파라미터 수와 실제 연산에 사용되는 파라미터 수가 분리된다. 예를 들어 Mixtral 8x7B는 총 47B 파라미터를 보유하지만, 각 토큰은 13B 파라미터만 사용한다. Dense 47B 모델 대비 연산량이 약 1/3.6로 줄어들면서도 품질은 유사하거나 더 높다.
Dense vs Sparse MoE 비교
| 항목 | Dense 모델 | Sparse MoE |
|---|---|---|
| 활성 파라미터 | 전체 파라미터 = 활성 파라미터 | 전체의 10-30%만 활성화 |
| FLOPs 효율 | 파라미터 수에 비례 | 활성 파라미터 수에 비례 |
| 메모리 요구량 | 파라미터 크기만큼 | 전체 파라미터 저장 필요 (메모리는 더 큼) |
| 학습 안정성 | 상대적으로 안정 | 라우팅 불안정, 전문가 붕괴 위험 |
| 확장성 | 선형 비용 증가 | 전문가 추가로 저비용 확장 |
| 추론 지연 | 예측 가능 | 라우팅 오버헤드, 전문가 로딩 지연 |
| 대표 모델 | LLaMA, GPT-4(추정) | Mixtral, DeepSeek-V3, Switch Transformer |
기본 MoE 레이어 구현
MoE 레이어의 기본 구조를 PyTorch로 구현하면 다음과 같다. 게이팅 네트워크가 입력 토큰에 대해 각 전문가의 가중치를 계산하고, Top-K 전문가를 선택하여 출력을 합산한다.
import torch
import torch.nn as nn
import torch.nn.functional as F
class Expert(nn.Module):
"""단일 전문가 FFN 모듈"""
def __init__(self, d_model: int, d_ff: int, dropout: float = 0.1):
super().__init__()
self.w1 = nn.Linear(d_model, d_ff)
self.w2 = nn.Linear(d_ff, d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.w2(self.dropout(F.gelu(self.w1(x))))
class MoELayer(nn.Module):
"""Mixture of Experts 레이어"""
def __init__(
self,
d_model: int,
d_ff: int,
num_experts: int,
top_k: int = 2,
dropout: float = 0.1,
):
super().__init__()
self.num_experts = num_experts
self.top_k = top_k
self.experts = nn.ModuleList([
Expert(d_model, d_ff, dropout) for _ in range(num_experts)
])
# 게이팅 네트워크: 입력 -> 전문가 점수
self.gate = nn.Linear(d_model, num_experts, bias=False)
def forward(self, x: torch.Tensor) -> torch.Tensor:
# x: (batch, seq_len, d_model)
batch_size, seq_len, d_model = x.shape
x_flat = x.view(-1, d_model) # (B*S, d_model)
# 게이팅 점수 계산
gate_logits = self.gate(x_flat) # (B*S, num_experts)
gate_probs = F.softmax(gate_logits, dim=-1)
# Top-K 전문가 선택
top_k_probs, top_k_indices = torch.topk(
gate_probs, self.top_k, dim=-1
) # (B*S, top_k)
# 선택된 전문가 가중치 재정규화
top_k_probs = top_k_probs / top_k_probs.sum(dim=-1, keepdim=True)
# 전문가별 출력 합산
output = torch.zeros_like(x_flat)
for k in range(self.top_k):
expert_idx = top_k_indices[:, k] # (B*S,)
weight = top_k_probs[:, k].unsqueeze(-1) # (B*S, 1)
for i in range(self.num_experts):
mask = (expert_idx == i)
if mask.any():
expert_input = x_flat[mask]
expert_output = self.experts[i](expert_input)
output[mask] += weight[mask] * expert_output
return output.view(batch_size, seq_len, d_model)
이 구현은 교육 목적이며, 실제 프로덕션에서는 Megablocks나 Tutel 같은 최적화 라이브러리를 사용하여 전문가별 배치 처리와 All-to-All 통신을 효율적으로 수행한다.
주요 논문 분석
Switch Transformer (Fedus et al., 2022)
Switch Transformer의 핵심 기여는 MoE 라우팅을 극단적으로 단순화한 것이다. 기존 MoE가 Top-2 이상의 전문가를 조합했다면, Switch Transformer는 각 토큰에 단 하나의 전문가만 배정하는 Switch Routing을 도입했다. 이 결정은 직관에 반하지만, 단일 전문가 라우팅이 통신 비용을 절반으로 줄이고 라우팅 연산 자체의 오버헤드도 감소시킨다는 실험 결과로 뒷받침되었다.
T5-Base 아키텍처 기반으로 동일 연산량 대비 최대 7배 사전학습 속도 향상을 달성했고, 조 단위 파라미터 모델까지 확장했다. bfloat16 혼합 정밀도 학습을 통해 메모리 효율도 개선했으며, 전문가 드롭아웃(Expert Dropout)과 보조 로드 밸런싱 손실을 함께 적용하여 학습 불안정성을 완화했다.
Switch Transformer가 도입한 전문가 용량 인자(Capacity Factor)는 각 전문가가 처리할 수 있는 최대 토큰 수를 결정한다. 용량을 초과하는 토큰은 드롭되며, 이는 학습 중 정보 손실을 유발하지만 연산 부하의 균형을 잡는 데 필수적이다.
GShard (Lepikhin et al., 2021)
GShard는 MoE를 대규모 분산 환경에서 실용적으로 학습시키기 위한 시스템 설계에 초점을 맞춘 논문이다. 600B 파라미터 다국어 번역 모델을 2048개 TPU v3 가속기에서 4일 만에 학습시켰다. 핵심 기술 기여는 세 가지다.
첫째, Expert Parallelism이다. 전문가를 여러 디바이스에 분산 배치하고, 토큰을 해당 전문가가 위치한 디바이스로 라우팅하는 All-to-All 통신 패턴을 정립했다. 둘째, Random Routing이다. Top-1 전문가를 결정론적으로 선택한 뒤, 두 번째 전문가는 게이트 가중치에 비례하는 확률로 확률적으로 선택한다. 이 방식은 학습 시 탐색(exploration)을 촉진한다. 셋째, XLA 컴파일러 확장을 통해 최소한의 코드 변경으로 다양한 병렬화 패턴을 표현할 수 있는 경량 API를 제공했다.
GShard의 그룹별 용량 계산 공식 C = 2N / (G * E)는 그룹 크기 G, 전문가 수 E, 토큰 수 N을 고려하여 각 전문가의 처리 용량을 동적으로 결정한다.
ST-MoE (Zoph et al., 2022)
ST-MoE의 이름이 의미하듯, 이 논문의 핵심 질문은 "MoE 모델을 안정적으로(Stable) 학습시키고 효과적으로 전이(Transferable)할 수 있는가"이다. 269B 파라미터 Sparse 모델(ST-MoE-32B)을 32B Dense 모델 수준의 연산 비용으로 학습시키면서, SuperGLUE, ARC, XSum 등 다양한 벤치마크에서 Sparse 모델 최초로 최고 성능을 달성했다.
학습 안정성을 위해 Router Z-Loss를 도입했다. 이 손실 함수는 라우터 로짓의 크기를 제어하여 학습 중 발산(divergence)을 방지한다. 기존 보조 손실(auxiliary loss)과 함께 사용하면 학습 안정성이 크게 개선된다. 또한 Top-2 라우팅, 학습 시 용량 인자 1.25, 코어당 최대 1개 전문가 배치 등 실전 설계 권장사항을 체계적으로 정리했다.
파인튜닝 시에는 전문가 드롭아웃 비율을 높이고, 배치 크기를 줄이며, 학습률을 보수적으로 설정하는 것이 전이 학습 품질 향상에 효과적이라는 실험 결과를 제시했다.
Mixtral 8x7B (Mistral AI, 2024)
Mixtral 8x7B는 오픈소스 MoE 모델의 실용성을 결정적으로 증명한 모델이다. Mistral 7B와 동일한 Transformer 아키텍처에서 각 레이어의 FFN을 8개 전문가로 교체하고, 토큰마다 Top-2 전문가를 선택한다. 총 47B 파라미터 중 13B만 활성화되므로, 추론 속도는 13B Dense 모델과 유사하면서 품질은 LLaMA 2 70B에 필적한다.
| 항목 | Mixtral 8x7B | LLaMA 2 70B | Mistral 7B |
|---|---|---|---|
| 총 파라미터 | 46.7B | 70B | 7.3B |
| 활성 파라미터 | 12.9B | 70B | 7.3B |
| 추론 FLOPs | ~26B | ~140B | ~15B |
| MMLU | 70.6 | 69.8 | 60.1 |
| ARC-Challenge | 66.4 | 64.6 | 55.5 |
| 전문가 수 | 8 | - | - |
| 활성 전문가 수 | 2 | - | - |
Mixtral의 전문가 특화 분석 결과, 전문가들은 도메인보다는 구문론적 패턴에 따라 특화되는 경향을 보였다. 특정 전문가가 코드나 수학에만 반응하는 것이 아니라, 문장 구조나 토큰 위치에 따라 전문가 선택이 달라진다는 것이다.
DeepSeek-MoE와 DeepSeek-V3
DeepSeek-MoE는 "궁극의 전문가 특화(Ultimate Expert Specialization)"를 목표로 두 가지 핵심 전략을 제시했다.
첫째, 세분화된 전문가 분할(Fine-Grained Expert Segmentation)이다. 기존 N개 전문가를 mN개로 세분화하되, 각 전문가의 FFN 중간 차원을 1/m로 줄여 총 파라미터 수를 동일하게 유지한다. 대신 mK개의 전문가를 활성화하여 더 유연한 전문가 조합이 가능하다. DeepSeek-MoE 16B는 LLaMA 2 7B와 유사한 성능을 연산량 40%만으로 달성했다.
둘째, 공유 전문가(Shared Expert) 격리다. 모든 토큰이 공통으로 사용하는 전문가를 별도로 분리하여, 라우팅 전문가 간 중복 지식 저장을 방지하고 전문가별 특화도를 높인다.
DeepSeek-V3는 이를 671B 파라미터로 확장하면서 혁신적인 보조 손실 없는(Auxiliary-Loss-Free) 로드 밸런싱을 도입했다. 256개 라우팅 전문가와 1개 공유 전문가를 사용하며, 토큰당 8개 전문가를 활성화한다. 기존 보조 손실 방식은 로드 밸런싱과 모델 성능 사이에 트레이드오프가 존재했지만, DeepSeek-V3는 각 전문가에 바이어스 항을 추가하고 학습 중 전문가 부하를 모니터링하여 바이어스를 동적으로 조정한다. 이 방식으로 학습 전체 과정에서 토큰 드롭 없이 균형 잡힌 로드를 유지한다.
MoE 모델 비교 종합
| 모델 | 총 파라미터 | 활성 파라미터 | 전문가 수 | 활성 전문가 | 라우팅 방식 | 로드 밸런싱 |
|---|---|---|---|---|---|---|
| Switch Transformer | 1.6T | ~1/E | 128 | 1 | Top-1 | 보조 손실 |
| GShard 600B | 600B | ~2/E | 2048 | 2 | Top-1 + Random | 용량 제한 + 보조 손실 |
| ST-MoE-32B | 269B | 32B 상당 | 64 | 2 | Top-2 | Router Z-Loss + 보조 손실 |
| Mixtral 8x7B | 46.7B | 12.9B | 8 | 2 | Top-2 | 보조 손실 |
| DeepSeek-V3 | 671B | 37B | 256+1 | 8+1 | Top-8 | Auxiliary-Loss-Free |
라우팅 메커니즘 심층 분석
Top-K 라우팅 구현
라우팅 메커니즘은 MoE의 핵심이다. 입력 토큰의 히든 상태를 받아 어떤 전문가를 활성화할지 결정하며, 이 결정의 품질이 모델 전체 성능을 좌우한다. 다양한 라우팅 전략의 구현을 살펴보자.
import torch
import torch.nn as nn
import torch.nn.functional as F
from typing import Tuple
class TopKRouter(nn.Module):
"""Top-K 라우팅 with 노이즈 주입"""
def __init__(
self,
d_model: int,
num_experts: int,
top_k: int = 2,
noise_std: float = 1.0,
):
super().__init__()
self.top_k = top_k
self.num_experts = num_experts
self.noise_std = noise_std
self.gate = nn.Linear(d_model, num_experts, bias=False)
def forward(
self, x: torch.Tensor
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""
Args:
x: (batch * seq_len, d_model)
Returns:
top_k_indices: (batch * seq_len, top_k)
top_k_weights: (batch * seq_len, top_k)
gate_logits: (batch * seq_len, num_experts)
"""
gate_logits = self.gate(x)
# 학습 시 노이즈 주입으로 탐색 촉진
if self.training:
noise = torch.randn_like(gate_logits) * self.noise_std
gate_logits = gate_logits + noise
gate_probs = F.softmax(gate_logits, dim=-1)
top_k_probs, top_k_indices = torch.topk(
gate_probs, self.top_k, dim=-1
)
# 재정규화
top_k_weights = top_k_probs / top_k_probs.sum(dim=-1, keepdim=True)
return top_k_indices, top_k_weights, gate_logits
라우팅 전략 비교
| 전략 | 논문 | 활성 전문가 | 장점 | 단점 |
|---|---|---|---|---|
| Top-1 | Switch Transformer | 1 | 최소 통신 비용, 단순 | 전문가 붕괴 위험 높음 |
| Top-2 | ST-MoE, Mixtral | 2 | 안정적, 성능 우수 | Top-1 대비 2배 통신 |
| Top-1 + Random | GShard | 2 | 탐색 촉진 | 비결정적 추론 |
| Top-K (K >= 4) | DeepSeek-V3 | 8 | 세분화된 전문가 활용 | 전문가 수 많아야 유효 |
| Expert Choice | EC Routing | 가변 | 완전 균형 보장 | 토큰별 전문가 수 불균일 |
Expert Choice Routing은 전통적 라우팅과 관점을 뒤집는다. 토큰이 전문가를 선택하는 대신, 각 전문가가 자신이 처리할 토큰을 선택한다. 이 방식은 로드 밸런싱을 완벽하게 보장하지만, 특정 토큰이 어떤 전문가에도 선택되지 않을 수 있다는 문제가 있다.
학습 안정성과 로드 밸런싱
로드 밸런싱 손실 구현
MoE 학습에서 가장 흔한 문제는 전문가 붕괴(Expert Collapse)다. 특정 전문가에 토큰이 집중되면 해당 전문가만 학습되고, 나머지 전문가는 활용되지 않아 사실상 Dead Expert가 된다. 이를 방지하기 위해 보조 로드 밸런싱 손실을 사용한다.
def load_balancing_loss(
gate_logits: torch.Tensor,
top_k_indices: torch.Tensor,
num_experts: int,
top_k: int = 2,
) -> torch.Tensor:
"""
Switch Transformer 스타일 로드 밸런싱 손실 계산
각 전문가에 할당된 토큰의 비율(f_i)과
게이트 확률의 평균(p_i)의 내적을 최소화한다.
균등 분포일 때 최솟값을 가진다.
Args:
gate_logits: (num_tokens, num_experts)
top_k_indices: (num_tokens, top_k)
num_experts: 전문가 수
top_k: 활성 전문가 수
Returns:
스칼라 로드 밸런싱 손실
"""
num_tokens = gate_logits.shape[0]
gate_probs = F.softmax(gate_logits, dim=-1)
# f_i: 각 전문가에 할당된 토큰 비율
# one-hot 인코딩으로 할당 카운트
expert_mask = F.one_hot(
top_k_indices, num_classes=num_experts
).float() # (num_tokens, top_k, num_experts)
expert_mask = expert_mask.sum(dim=1) # (num_tokens, num_experts)
tokens_per_expert = expert_mask.sum(dim=0) # (num_experts,)
f = tokens_per_expert / (num_tokens * top_k)
# p_i: 각 전문가에 대한 평균 게이트 확률
p = gate_probs.mean(dim=0) # (num_experts,)
# 밸런싱 손실: num_experts * sum(f_i * p_i)
balance_loss = num_experts * (f * p).sum()
return balance_loss
Router Z-Loss
ST-MoE에서 도입한 Router Z-Loss는 라우터 로짓의 크기를 제어하여 학습 발산을 방지한다. 로짓 값이 지나치게 커지면 softmax의 기울기가 사라지면서 학습이 불안정해지는데, Z-Loss가 이를 억제한다.
def router_z_loss(gate_logits: torch.Tensor) -> torch.Tensor:
"""
ST-MoE Router Z-Loss 구현
라우터 로짓의 log-sum-exp 값의 제곱 평균을 최소화.
로짓이 과도하게 커지는 것을 방지하여 학습 안정성 확보.
Args:
gate_logits: (num_tokens, num_experts)
Returns:
스칼라 z-loss 값
"""
# log(sum(exp(x)))의 제곱
log_z = torch.logsumexp(gate_logits, dim=-1) # (num_tokens,)
z_loss = (log_z ** 2).mean()
return z_loss
# 학습 루프에서의 통합
def compute_moe_loss(
task_loss: torch.Tensor,
gate_logits: torch.Tensor,
top_k_indices: torch.Tensor,
num_experts: int,
alpha_balance: float = 0.01,
alpha_z: float = 0.001,
) -> torch.Tensor:
"""
전체 MoE 손실 = 태스크 손실 + 밸런싱 손실 + Z-Loss
"""
bal_loss = load_balancing_loss(
gate_logits, top_k_indices, num_experts
)
z_loss = router_z_loss(gate_logits)
total_loss = task_loss + alpha_balance * bal_loss + alpha_z * z_loss
return total_loss
alpha_balance와 alpha_z의 설정이 중요하다. 보조 손실 계수가 너무 크면 모델 성능이 하락하고, 너무 작으면 로드 밸런싱 효과가 없어 전문가 붕괴가 발생한다. ST-MoE는 alpha_balance = 0.01, alpha_z = 0.001을 권장했다.
Auxiliary-Loss-Free 접근법
DeepSeek-V3가 도입한 보조 손실 없는 로드 밸런싱은 이 트레이드오프를 근본적으로 해소한다. 각 전문가에 학습 가능한 바이어스 항을 추가하고, 학습 중 전문가별 부하를 실시간 모니터링하여 바이어스를 조정한다.
class AuxLossFreeRouter(nn.Module):
"""DeepSeek-V3 스타일 Auxiliary-Loss-Free 라우터"""
def __init__(
self,
d_model: int,
num_experts: int,
top_k: int = 8,
bias_update_speed: float = 0.001,
):
super().__init__()
self.num_experts = num_experts
self.top_k = top_k
self.gamma = bias_update_speed
self.gate = nn.Linear(d_model, num_experts, bias=False)
# 바이어스는 그래디언트 업데이트가 아닌 규칙 기반 조정
self.register_buffer(
"expert_bias", torch.zeros(num_experts)
)
self.register_buffer(
"target_load", torch.ones(num_experts) / num_experts
)
def forward(self, x: torch.Tensor):
gate_logits = self.gate(x)
# 바이어스를 더해 라우팅 결정에 반영
biased_logits = gate_logits + self.expert_bias.unsqueeze(0)
gate_probs = F.softmax(gate_logits, dim=-1)
# Top-K 선택은 바이어스 포함 로짓으로 수행
_, top_k_indices = torch.topk(
biased_logits, self.top_k, dim=-1
)
# 가중치는 원본 게이트 확률에서 추출
top_k_probs = torch.gather(gate_probs, 1, top_k_indices)
top_k_weights = top_k_probs / top_k_probs.sum(dim=-1, keepdim=True)
# 학습 시 바이어스 업데이트 (그래디언트 불필요)
if self.training:
with torch.no_grad():
expert_load = F.one_hot(
top_k_indices.view(-1), self.num_experts
).float().sum(0)
expert_load = expert_load / expert_load.sum()
# 과부하 전문가는 바이어스 감소, 저부하 전문가는 증가
self.expert_bias += self.gamma * (
self.target_load - expert_load
)
return top_k_indices, top_k_weights, gate_logits
이 방식의 핵심은 라우팅 결정(Top-K 선택)에는 바이어스를 반영하지만, 전문가 출력의 가중치 계산에는 원본 게이트 확률을 사용한다는 점이다. 바이어스가 모델의 그래디언트 흐름에 간섭하지 않으므로 성능 저하 없이 로드 밸런싱을 달성한다.
추론 최적화
Expert Offloading과 Capacity Budgeting
MoE 모델의 추론에서 가장 큰 도전은 메모리다. 모든 전문가를 GPU 메모리에 상주시키기 어려운 경우, 전문가 오프로딩(Expert Offloading)이 필수적이다. 활성화되지 않는 전문가를 CPU 메모리나 디스크에 내려놓고, 필요할 때만 GPU로 로딩하는 전략이다.
2025년부터 등장한 연구들은 투기적 디코딩(Speculative Decoding)과 전문가 오프로딩을 결합하여 성능을 크게 개선했다. SpecMoEOff는 투기적 디코딩으로 전문가 작업량을 확대하여 오프로딩 지연을 숨기고, 최대 2.5배 디코드 처리량 향상을 달성했다. MoE-Spec은 학습 없이 각 레이어의 전문가 용량을 고정하여 자주 사용되지 않는 전문가를 드롭함으로써 10-30% 처리량 향상을 보였다.
from dataclasses import dataclass
from typing import Dict, Optional
import torch
@dataclass
class ExpertCacheConfig:
"""전문가 캐시 설정"""
gpu_cache_size: int = 4 # GPU에 상주할 전문가 수
prefetch_count: int = 2 # 선제 로딩할 전문가 수
device: str = "cuda:0"
class ExpertOffloadManager:
"""
전문가 오프로딩 관리자.
LRU 기반으로 GPU 캐시를 관리하고,
다음 레이어 전문가를 선제 로딩한다.
"""
def __init__(
self,
experts: Dict[int, torch.nn.Module],
config: ExpertCacheConfig,
):
self.experts = experts
self.config = config
self.gpu_cache: Dict[int, torch.nn.Module] = {}
self.access_order: list = []
def get_expert(self, expert_id: int) -> torch.nn.Module:
"""전문가를 GPU로 로딩 (LRU 캐시)"""
if expert_id in self.gpu_cache:
self.access_order.remove(expert_id)
self.access_order.append(expert_id)
return self.gpu_cache[expert_id]
# GPU 캐시가 가득 찬 경우 LRU 전문가 제거
if len(self.gpu_cache) >= self.config.gpu_cache_size:
evict_id = self.access_order.pop(0)
self.gpu_cache[evict_id].cpu()
del self.gpu_cache[evict_id]
# 전문가를 GPU로 이동
expert = self.experts[expert_id].to(self.config.device)
self.gpu_cache[expert_id] = expert
self.access_order.append(expert_id)
return expert
def prefetch(self, predicted_expert_ids: list):
"""다음 레이어에서 사용될 전문가를 비동기 선제 로딩"""
for eid in predicted_expert_ids[:self.config.prefetch_count]:
if eid not in self.gpu_cache:
self.get_expert(eid)
양자화와 Expert Pruning
MoE 모델의 추론 효율을 높이는 또 다른 축은 양자화와 전문가 프루닝이다. 모든 전문가를 동일한 비트로 양자화하는 대신, 자주 활성화되는 전문가는 고정밀도로 유지하고 드물게 사용되는 전문가는 공격적으로 양자화하는 적응형 양자화 전략이 효과적이다. Expert Pruning은 학습 후 활성화 빈도가 낮은 전문가를 완전히 제거하여 메모리 사용량을 줄인다. Mixtral 8x7B에서 2개 전문가를 프루닝해도 성능 하락이 미미하다는 실험 결과가 보고된 바 있다.
트러블슈팅
MoE 모델 학습과 추론에서 빈번하게 발생하는 문제와 해결책을 정리한다.
전문가 붕괴 (Expert Collapse)
증상: 학습 중 소수의 전문가만 활성화되고 나머지 전문가의 게이트 확률이 0에 수렴한다. 텐서보드에서 전문가별 토큰 할당 비율을 모니터링하면 특정 전문가에 90% 이상 토큰이 집중되는 것을 확인할 수 있다.
원인은 대부분 보조 손실 계수가 너무 작거나, 학습 초기 라우터 가중치 초기화가 불균형한 경우다. 해결책으로 alpha_balance를 0.01에서 시작하여 점진적으로 조정하고, 라우터 가중치를 작은 값으로 균일 초기화하며, 학습 초기 일정 스텝 동안 라우팅을 랜덤으로 수행하는 워밍업을 적용한다.
학습 발산 (Training Divergence)
증상: 학습 중 loss가 갑자기 급증하거나 NaN이 발생한다. MoE 모델에서 특히 bfloat16 학습 시 빈번하다.
Router Z-Loss를 적용하고, 라우터 로짓에 대해 별도의 float32 연산을 유지하는 것이 효과적이다. 학습률을 Dense 모델 대비 2-5배 낮추는 것도 권장된다.
All-to-All 통신 병목
증상: 분산 학습 시 전문가 병렬화의 All-to-All 통신이 전체 학습 시간의 30% 이상을 차지한다.
전문가 수를 디바이스 수의 배수로 설정하고, 용량 인자를 줄여 전송 데이터량을 감소시킨다. 통신과 연산을 오버랩하는 파이프라인 스케줄링도 효과적이다. Megablocks 라이브러리는 블록 희소 연산을 통해 All-to-All 통신을 회피하는 접근법을 제공한다.
추론 시 메모리 부족
증상: 전문가가 모두 GPU 메모리에 적재되지 않아 OOM이 발생한다.
Expert Offloading을 적용하거나, 전문가 프루닝으로 메모리 요구량 자체를 줄인다. 텐서 병렬화(Tensor Parallelism)와 전문가 병렬화를 조합하여 여러 GPU에 분산시키는 것도 방법이다.
실패 사례
보조 손실 계수 과다 설정
한 팀이 Mixtral 아키텍처 기반 커스텀 MoE 모델을 학습하면서 전문가 붕괴를 방지하기 위해 alpha_balance를 0.1로 높게 설정했다. 로드 밸런싱은 완벽했지만, 모델이 토큰의 의미와 무관하게 전문가를 균등하게 분배하는 방향으로 학습되면서 실제 태스크 성능이 Dense 모델보다 낮아졌다. 보조 손실이 주 손실을 압도하여 라우터가 의미 있는 전문가 특화를 학습하지 못한 것이다. alpha_balance를 0.005로 낮추고 Router Z-Loss를 함께 적용한 후에야 성능이 개선되었다.
추론 시 Top-1 전환의 함정
학습 시 Top-2 라우팅을 사용한 모델의 추론 비용을 줄이기 위해 Top-1으로 전환한 사례가 있다. 연산량은 절반으로 줄었지만, 벤치마크 점수가 8-12% 하락했다. 두 전문가의 출력이 상호 보완적으로 학습되었기 때문에, 하나만 사용하면 표현력이 크게 손실된다. 추론 비용 절감이 목적이라면 학습 단계에서부터 Top-1으로 설계하거나, 양자화와 프루닝으로 접근해야 한다.
공유 전문가 없는 세분화 실패
DeepSeek-MoE의 세분화된 전문가 분할을 적용하면서 공유 전문가(Shared Expert)를 생략한 경우가 있다. 모든 전문가가 기본적인 언어 패턴(관사, 전치사, 구두점 등)을 중복 학습하면서 전문가당 고유 지식의 양이 줄었고, 세분화의 이점이 상쇄되었다. 공유 전문가가 공통 지식을 흡수해야 라우팅 전문가가 고유한 패턴에 집중할 수 있다.
운영 체크리스트
MoE 모델을 학습부터 서빙까지 운영할 때 확인해야 할 항목을 정리한다.
학습 설정
- 라우터 가중치는 작은 표준편차(0.01 이하)로 초기화했는가
- 보조 손실 계수(alpha_balance)를 0.005-0.01 범위에서 시작했는가
- Router Z-Loss를 활성화했는가
- 학습률이 동일 크기 Dense 모델 대비 보수적(2-5배 낮음)인가
- bfloat16 학습 시 라우터 로짓 연산은 float32로 유지하는가
- 전문가 수가 디바이스 수의 배수인가
- 용량 인자(Capacity Factor)를 1.0-1.5 범위로 설정했는가
모니터링
- 전문가별 토큰 할당 비율을 실시간 추적하고 있는가
- Dead Expert(할당 비율 1% 미만) 수를 경보로 설정했는가
- 라우터 로짓의 평균 크기와 분산을 추적하고 있는가
- All-to-All 통신 시간 대비 연산 시간 비율을 측정하고 있는가
- 학습 loss의 급격한 변동(spike)에 대한 자동 감지가 있는가
추론 서빙
- 전문가 오프로딩 전략을 결정했는가 (전량 GPU / 부분 오프로딩 / 디스크)
- 양자화 수준을 전문가별로 차등 적용했는가 (자주 쓰이는 전문가는 고정밀도)
- 전문가 프루닝 후 성능 저하를 벤치마크로 확인했는가
- 배치 크기에 따른 라우팅 오버헤드를 측정했는가
- 전문가 캐시 적중률(Hit Rate)을 모니터링하고 있는가
- 투기적 디코딩 적용 시 Draft 모델과 전문가 예측 정확도를 확인했는가
파인튜닝
- 전문가 드롭아웃 비율을 사전학습 대비 높였는가 (ST-MoE 권장)
- 배치 크기를 사전학습 대비 줄였는가
- 라우터를 고정(freeze)하고 전문가만 파인튜닝하는 옵션을 실험했는가
- 파인튜닝 후 전문가 특화 분포가 태스크에 맞게 변화했는가 확인했는가
참고자료
- Switch Transformers: Scaling to Trillion Parameter Models with Simple and Efficient Sparsity (Fedus et al., 2022)
- GShard: Scaling Giant Models with Conditional Computation and Automatic Sharding (Lepikhin et al., 2021)
- ST-MoE: Designing Stable and Transferable Sparse Expert Models (Zoph et al., 2022)
- Mixtral of Experts (Mistral AI, 2024)
- DeepSeekMoE: Towards Ultimate Expert Specialization in Mixture-of-Experts Language Models (2024)
- DeepSeek-V3 Technical Report (2024)
- Mixture-of-Experts with Expert Choice Routing (Zhou et al., 2022)
- A Review on the Evolvement of Load Balancing Strategy in MoE LLMs
- Applying Mixture of Experts in LLM Architectures - NVIDIA Technical Blog