🤔 왜 지금 MoE인가?
LLM 생태계는 최근 몇 년간 '밀집(Dense) 모델'의 스케일링 법칙에 크게 의존해왔습니다. 파라미터 수와 데이터만 늘리면 성능이 올라간다는 단순한 공식이었죠. 하지만 GPT-4, Llama 3, DeepSeek V3/R1 같은 모델들이 수백억~수천억 개의 파라미터를 가지면서, 밀집 모델의 한계가 명확해졌습니다.
- 훈련 비용 폭등: 모든 파라미터를 항상 활성화하므로 FLOP 대비 효율이 떨어짐
- 추론 지연: 모델 크기가 커질수록 토큰당 처리 시간이 선형적으로 증가
- 배포 난이도: 단일 GPU에 모델을 올리는 것조차 불가능해짐
Mixture of Experts (MoE) 는 이 문제에 대한 현실적인 해결책입니다. 모든 파라미터를 항상 사용하는 대신, 입력 토큰별로 필요한 전문가(Expert) 서브네트워크만 선택적으로 활성화합니다. 결과적으로 전체 파라미터 수는 수백억 개지만, 추론 시 활성화되는 파라미터는 수십억 개에 불과합니다.
예를 들어 gpt-oss-20b 모델은 총 210억 개의 파라미터를 가지지만, 토큰당 4개의 Expert만 활성화하여 실질적으로는 약 36억 개의 활성 파라미터로 동작합니다. M3 Ultra Mac(메모리 대역폭 ~800GB/s)에서 bfloat16 기준으로 추론 속도를 계산해보면:
추론 속도 ≈ 800 / (3.6 × 2) ≈ 111 tokens/s
실제 측정값은 약 115 tok/s로, 이론값과 거의 일치합니다. 즉, 210억 파라미터 모델의 성능을 36억 파라미터 수준의 속도로 사용할 수 있는 셈이죠.
참고자료: 이 글의 핵심 내용은 Hugging Face 공식 블로그 포스트를 기반으로 재구성했습니다.

🔧 Transformers 라이브러리 v5: MoE를 위한 3대 핵심 변화
Hugging Face transformers 라이브러리는 원래 밀집 모델을 기준으로 설계되었습니다. MoE 모델이 급증하면서(DeepSeek R1 이후 폭발적 증가), 라이브러리 내부의 가정들을 완전히 다시 짜야 했습니다. v5에서 도입된 세 가지 핵심 변화를 살펴보겠습니다.
1. Weight Loading Refactor: 체크포인트와 런타임의 불일치 해소
MoE 체크포인트를 열어보면 Expert가 개별적으로 저장되어 있습니다.
// DeepSeek-V3 체크포인트 인덱스 예시
{
"model.layers.3.mlp.experts.0.gate_proj.weight": ...,
"model.layers.3.mlp.experts.1.gate_proj.weight": ...,
// ... 총 256개 Expert
"model.layers.3.mlp.experts.255.gate_proj.weight": ...
}
하지만 GPU에서 효율적으로 연산하려면(Grouped GEMM, Fused MoE 커널) 모든 Expert 가중치가 하나의 연속된 텐서로 패킹되어야 합니다. 즉, 체크포인트는 256개의 분리된 텐서지만, 런타임은 1개의 패킹된 텐서를 원하는 거죠.
이 문제를 해결하기 위해 도입된 것이 WeightConverter 추상화입니다. 핵심 아이디어는:
체크포인트는 단순한 텐서의 직렬화된 소스일 뿐이다. 로딩은 이 텐서들을 원하는 런타임 레이아웃으로 변환하는 파이프라인이다.
from transformers.modeling_utils import WeightConverter, MergeModulelist, Concatenate
# 예: Expert 가중치를 하나의 텐서로 합치기
converter = WeightConverter(
source_patterns=[
"block_sparse_moe.experts.*.w1.weight",
"block_sparse_moe.experts.*.w3.weight",
],
target_key="mlp.experts.gate_up_proj",
operations=[
MergeModulelist(dim=0), # Expert 리스트를 하나의 텐서로
Concatenate(dim=1), # gate와 up projection을 결합
],
)
이 변환은 지연(lazy) 방식으로 수행됩니다. 로더는 체크포인트 키를 한 번 스캔한 후, 컨버터 패턴과 매칭되는 키를 그룹화하고, 각 컨버터의 의존성이 준비되는 즉시 스레드 풀에서 비동기로 실행합니다. 예를 들어 MergeModulelist는 해당 레이어의 모든 Expert가 로드될 때까지 기다렸다가 한 번에 합칩니다.
벤치마크 결과 (Qwen/Qwen1.5-110B-Chat, A100 80GB 1대):
| 버전 | 전략 | 로딩 모드 | 시간 |
|---|---|---|---|
| v4.57.6 | device_map="auto" | Threadpool | 66.24s |
| v4.57.6 | device_map="auto" | Sequential | 67.29s |
| v4.57.6 | TP | — | OOM |
| v5 | device_map="auto" | Async (default) | 20.71s |
| v5 | device_map="auto" | Sync | 45.3s |
| v5 | TP | Async | 10.1s |
| v5 | TP | Sync | 19.28s |
단순히 '스레드를 더 많이 써서' 빨라진 것이 아닙니다. 단일 패스 라우팅, 비동기 구체화, 변환 인식 스케줄링의 조합 덕분에 메모리 피크를 피하면서 Expert 패킹과 프로젝션 퓨전을 로드 타임에 처리할 수 있게 되었습니다.
2. Expert Backend: 플러그인 가능한 실행 아키텍처
Expert가 하나의 텐서로 패킹된 후에는 어떻게 효율적으로 라우팅할지가 문제입니다. MoE 모델은 각 토큰을 서로 다른 Expert로 보내야 하므로, 디스패치 → 연산 → 수집 및 재정렬 과정이 필요합니다.
transformers v5에서는 @use_experts_implementation 데코레이터 패턴을 도입하여 Expert 계산을 모델 구현에서 분리했습니다. 현재 세 가지 백엔드를 제공합니다:
eager: 선택된 Expert를 루프로 돌며 하나씩 연산. 정확성 참조 및 디버깅용.batched_mm:torch.bmmAPI 사용. 선택된 Expert 가중치를 토큰별로 복제한 후 단일 배치 GEMM 수행. 소규모 배치, GPU 메모리가 넉넉할 때 적합.grouped_mm:torch._grouped_mmAPI 사용. 토큰을 Expert ID별로 정렬 및 그룹화 후 단일 Grouped GEMM 수행. 대규모 배치나 메모리 제약 환경에서 빛을 발함.
3. Expert Parallelism: 수백억 파라미터 모델을 여러 GPU에 분산
MoE 모델은 단일 GPU에 전혀 들어가지 않을 정도로 클 수 있습니다. Expert Parallelism(EP)은 Expert를 여러 디바이스에 분산하여 각 디바이스가 자신에게 할당된 Expert만 로드하고 계산한 후 결과를 집계합니다.
import torch
from transformers import AutoModelForCausalLM
from transformers.distributed.configuration_utils import DistributedConfig
# Expert Parallelism 활성화
distributed_config = DistributedConfig(enable_expert_parallel=True)
model = AutoModelForCausalLM.from_pretrained(
"openai/gpt-oss-120b",
dtype="auto",
distributed_config=distributed_config,
)
# 실행: Expert 수가 GPU 수로 나누어 떨어져야 함
torchrun --nproc-per-node=8 script.py
EP의 핵심 컴포넌트:
GroupedGemmParallel: Expert 차원(dim=0)을 따라 가중치를 분할. 각 디바이스는num_experts / num_devices개의 Expert만 로드.RouterParallel: 글로벌 Expert 인덱스를 로컬 인덱스로 재매핑하고, 현재 랭크에 할당되지 않은 Expert는 마스킹. all-reduce로 부분 출력을 결합.

⚠️ MoE, 이럴 때는 조심하세요
MoE는 만능 해결책이 아닙니다. 실제 프로젝트에 도입하기 전에 다음 사항을 꼭 확인하세요.
1. 추론 배치 크기에 따른 성능 역전
batched_mm 백엔드는 배치가 작을 때 빠르지만, 배치가 커지면 메모리 사용량이 폭발합니다. 반대로 grouped_mm은 배치가 클 때 효율적이지만, 정렬 오버헤드가 있습니다. 서비스의 예상 동시 요청 수에 따라 백엔드를 선택해야 합니다.
2. Expert 간 불균형 (Load Balancing)
라우터가 특정 Expert만 편애하면 해당 Expert의 가중치만 업데이트되고 나머지는 죽은 Expert가 됩니다. DeepSeek R1은 auxiliary loss를 사용하여 이 문제를 완화했지만, 완전히 해결된 것은 아닙니다. 훈련 시 모니터링이 필수입니다.
3. 양자화(Quantization)의 복잡성
Expert별로 양자화를 적용하려면 Expert가 예측 가능한 패킹 레이아웃으로 존재해야 합니다. v5의 WeightConverter가 이 파이프라인을 가능하게 했지만, 아직 모든 양자화 기법(AWQ, GPTQ 등)이 MoE에 완전히 최적화된 것은 아닙니다.
4. 국내 클라우드 환경에서의 적용 맥락
한국 클라우드(Naver Cloud, KT Cloud, Kakao i Cloud)는 아직 NVIDIA H100/H200 클러스터의 보급률이 낮습니다. A100 80GB 환경이 주류이므로, Expert 수가 8개를 초과하는 모델(예: DeepSeek-V3의 256 Expert)은 Expert Parallelism을 제대로 활용하기 어려울 수 있습니다. 우선은 8개 이하의 Expert를 가진 모델(Mixtral 8x7B 등)로 PoC를 진행하는 것을 추천합니다.

🚀 다음 단계: 직접 실습해보기
이론은 충분히 다뤘습니다. 이제 직접 MoE 모델을 파인튜닝해보는 건 어떨까요?
함께 보면 좋은 글
MoE 아키텍처는 앞으로 LLM 생태계의 표준이 될 가능성이 높습니다. 특히 DeepSeek R1 이후 오픈소스 진영에서 MoE 채택이 가속화되고 있고, transformers 라이브러리도 이에 발맞춰 진화하고 있습니다. 단순히 '모델이 크다'는 이유로 MoE를 선택하기보다는, 자신의 서비스 특성(지연 시간, 처리량, 비용)에 맞는 Expert 수와 활성화 전략을 실험해보시길 권장합니다.
질문이나 의견이 있다면 댓글로 남겨주세요. 함께 논의해보면 좋을 것 같습니다! 😊