2. Mixture-of-LoRAs
개요
mixture_lora는 FFN의 각 Linear 레이어를 여러 개의 LoRA 전문가(expert) + 라우터로 교체하는 전략입니다. 토큰마다 라우터가 top-k개의 LoRA 전문가를 선택하여 가중합을 계산합니다. dense_lora가 "하나의 LoRA"라면, mixture_lora는 "E개의 LoRA + 선택 메커니즘"입니다.
- 적합한 용도: 멀티태스크 학습, 적응형 LoRA, 다양한 입력 패턴 처리
- 호환 모델: Qwen, LLaMA, Mixtral (모든 백본)
- 참조 프리셋:
configs/presets/qwen3.5_0.8b_mixture_lora_sft.yml - dense_lora와의 핵심 차이: Linear → MixtureLoRALinear (라우터 + E개 LoRA branch)
사전 요구 사항
- EulerForge 설치 완료 (시작 가이드 참조)
- 데이터 전처리 완료 (
data/sft_10k_raw.jsonl생성) dense_lora튜토리얼을 먼저 읽으면 이해에 도움됩니다.
1. Where: 어디에 주입되는가?
dense_lora와 동일한 위치에 주입됩니다. 차이는 무엇으로 교체하는가입니다.
탐색 과정
BackboneAdapter.find_transformer_layers(model)— 트랜스포머 블록 탐색- 각 블록 내에서
target_keywords에 매칭되는 FFNnn.Linear탐색 DenseLoRAInjection클래스가build_mixture_lora_for_ffn_layers()호출- (선택) 어텐션 프로젝션에는 일반
LoRALinear적용 (MoE가 아닌 단일 LoRA)
타겟 모듈
| 영역 | 타겟 키워드 | 변환 결과 |
|---|---|---|
| FFN | gate_proj, up_proj, down_proj |
→ MixtureLoRALinear (라우터 + E개 LoRA) |
| Attention | q_proj, v_proj |
→ LoRALinear (단일 LoRA, MoE 아님) |
관련 설정
backbone: qwen3
injection:
strategy: mixture_lora
target_keywords: [gate_proj, up_proj, down_proj]
start_layer: 0
num_layers: 0
attn_lora:
enabled: true
keywords: [q_proj, v_proj]
2. What: 무엇이 주입되는가?
각 FFN의 타겟 nn.Linear가 MixtureLoRALinear로 교체됩니다.
변환 과정
변환 전: nn.Linear(in_features, out_features)
변환 후: MixtureLoRALinear
├── base_layer: nn.Linear (frozen, 원본 가중치)
├── router: nn.Linear(in_features → num_experts) ← 학습 대상
├── experts: [LoRABranch × num_experts] ← 학습 대상
│ ├── expert[0]: lora_A(r, in) + lora_B(out, r)
│ ├── expert[1]: lora_A(r, in) + lora_B(out, r)
│ ├── expert[2]: ...
│ └── expert[3]: ...
└── scaling: alpha / r
Forward 동작
[입력 x] ─────────┬── base_layer(x) ──────────────── base_out
│
├── router(x) → logits (batch, E)
│ └── softmax → gate_prob (batch, E)
│ └── top-k 선택 → 가중치 w_k, 인덱스 idx_k
│
└── 선택된 전문가만 실행:
delta = Σ (w_k * expert[idx_k](x))
최종 출력 = base_out + delta
예시 (num_experts=4, top_k=2): 토큰마다 4개 전문가 중 2개를 선택하여 가중합을 계산합니다. 선택되지 않은 전문가는 연산하지 않습니다.
관련 설정
injection:
strategy: mixture_lora
lora_r: 48 # 전문가별 LoRA 랭크
lora_alpha: 96 # 스케일링 팩터
lora_dropout: 0.05 # LoRA 드롭아웃
num_experts: 4 # LoRA 전문가 수 (E)
top_k: 2 # 토큰당 선택할 전문가 수 (K)
파라미터 가이드:
- num_experts: 전문가 수. 클수록 다양한 패턴 학습, 메모리 비용 증가. 일반적으로 4~8.
- top_k: 토큰당 활성 전문가 수. top_k <= num_experts여야 합니다. 보통 1~2.
3. When: 언제 어떤 파라미터를 훈련하는가?
mixture_lora는 2페이즈 스케줄을 사용합니다. 라우터를 먼저 웜업한 후 LoRA를 훈련합니다.
페이즈 구성
training:
phases:
- step: 0 # 페이즈 0: 라우터 웜업
trainable: ["router"]
- step: 2000 # 페이즈 1: LoRA 훈련
trainable: ["lora", "attn_lora"]
타임라인
Step 0 ──────────> Step 2000 ──────────────────────> Step 10000
│ │
│ 페이즈 0 │ 페이즈 1
│ [router 웜업] │ [lora + attn_lora 훈련]
│ router: trainable │ router: frozen
│ lora: frozen │ lora: trainable
│ attn_lora: frozen │ attn_lora: trainable
│ │
│ └── 옵티마이저 자동 재구축
왜 2페이즈인가?
-
페이즈 0 (라우터 웜업): 라우터가 안정적인 전문가 선택 패턴을 먼저 학습합니다. 각 LoRA expert의
lora_B는std=0.01의 small-random-init으로 초기화되어, 서로 다른 출력을 생성합니다 (symmetry-breaking). 이를 통해 라우터가 전문가 간 차이를 감지하고, 어떤 전문가를 어떤 입력에 할당할지 의미 있는 gradient를 받을 수 있습니다. -
페이즈 1 (LoRA 훈련): 라우터가 안정화된 후, 각 LoRA 전문가가 할당된 역할에 맞게 파라미터를 학습합니다. 라우터는 동결됩니다.
페이즈 전환 시 동작
스텝 2000에서 maybe_step()이 True를 반환하면:
1. 모든 파라미터를 requires_grad=False로 설정 (replace 모드)
2. lora, attn_lora 그룹만 requires_grad=True로 전환
3. 옵티마이저가 새로운 학습 가능 파라미터만으로 재구축됨
4. MoE 안정성 설정
mixture_lora 전략은 moe 섹션이 필수입니다.
moe:
router_z_loss_coef: 0.001 # Router z-loss 계수
load_balance:
type: aux_loss # 부하 분산 방식
aux_loss_coef: 0.01 # 보조 손실 계수
router_dtype: float32 # 라우터 연산 정밀도
각 파라미터의 역할
| 파라미터 | 역할 | 권장값 |
|---|---|---|
router_z_loss_coef |
라우터 로짓의 크기를 억제하여 softmax 오버플로우 방지 (ST-MoE 논문) | 0.001 |
load_balance.type |
전문가 간 부하 분산 방식. aux_loss는 보조 손실을 추가하여 특정 전문가에 토큰이 쏠리는 것을 방지 |
aux_loss |
load_balance.aux_loss_coef |
보조 손실의 가중치. 너무 크면 메인 태스크 성능 저하, 너무 작으면 부하 불균형 | 0.01 |
router_dtype |
라우터 softmax 연산 정밀도. float16/bfloat16은 수치 불안정 위험 |
float32 |
5. 전체 설정 파일 해설
configs/presets/qwen3.5_0.8b_mixture_lora_sft.yml 전문:
# ── 모델 정보 ──
device: cuda:0 # GPU 디바이스
backbone: qwen3 # [Where] Qwen3Adapter 사용
model_name: Qwen/Qwen3.5-0.8B-Base # HuggingFace 모델 ID
# ── 인젝션 설정 ──
injection:
strategy: mixture_lora # [What] Mixture-of-LoRAs 전략
lora_r: 48 # [What] 전문가별 LoRA 랭크
lora_alpha: 96 # [What] 스케일링 (96/48 = 2.0)
lora_dropout: 0.05 # [What] LoRA 드롭아웃
num_experts: 4 # [What] LoRA 전문가 수
top_k: 2 # [What] 토큰당 활성 전문가 수
target_keywords: [gate_proj, up_proj, down_proj] # [Where] FFN 타겟
start_layer: 0 # [Where] 시작 레이어
num_layers: 0 # [Where] 0 = 전체
attn_lora: # [Where] 어텐션 LoRA (단일)
enabled: true
keywords: [q_proj, v_proj]
# ── MoE 안정성 설정 ──
moe:
router_z_loss_coef: 0.001 # z-loss: 로짓 오버플로우 방지
load_balance:
type: aux_loss # 보조 손실 기반 부하 분산
aux_loss_coef: 0.01 # 보조 손실 가중치
router_dtype: float32 # 라우터 정밀도
# ── 훈련 설정 ──
training:
type: sft # SFT 훈련
phases: # [When] 2페이즈 스케줄
- step: 0 # 페이즈 0: 라우터 웜업
trainable: ["router"]
- step: 2000 # 페이즈 1: LoRA 훈련
trainable: ["lora", "attn_lora"]
lr: 1.0e-5
weight_decay: 0.01
warmup_steps: 200
max_train_steps: 10000 # dense_lora(5000)보다 김 (2페이즈)
batch_size: 4
grad_accum_steps: 4
max_grad_norm: 1.0
log_steps: 50
save_steps: 1000
val_steps: 500
6. 체크포인트 저장 구조
훈련 완료 시 체크포인트에는 base weight(1개) + router + N개 LoRA expert가 Linear별로 저장됩니다. FFN 구조 자체(gate_proj, up_proj, down_proj)는 변경되지 않습니다.
체크포인트 구조:
├── layer.N.mlp.gate_proj.base_layer.weight ← 원본 weight 1개 (freeze, 공유)
├── layer.N.mlp.gate_proj.router.weight ← per-Linear 라우터 (학습됨)
├── layer.N.mlp.gate_proj.experts.0.lora_A ← Expert 0 LoRA
├── layer.N.mlp.gate_proj.experts.0.lora_B
├── layer.N.mlp.gate_proj.experts.1.lora_A ← Expert 1 LoRA
├── layer.N.mlp.gate_proj.experts.1.lora_B
├── layer.N.mlp.gate_proj.experts.2.lora_A ← Expert 2 LoRA
├── layer.N.mlp.gate_proj.experts.2.lora_B
├── layer.N.mlp.gate_proj.experts.3.lora_A ← Expert 3 LoRA
├── layer.N.mlp.gate_proj.experts.3.lora_B
├── (up_proj, down_proj도 동일 패턴)
└── (attn_lora는 단일 LoRA: base_layer + lora_A + lora_B)
moe_expert_lora와의 핵심 차이
| 항목 | mixture_lora |
moe_expert_lora |
|---|---|---|
| base weight | 1개 (모든 expert가 공유) | N개 (expert마다 독립 사본) |
| expert 단위 | LoRA branch (lora_A + lora_B) | FFN 전체 (gate_proj + up_proj + down_proj) |
| router 위치 | per-Linear (gate_proj마다 1개) | per-MoEFFN (MLP 레벨 1개) |
| 메모리 | 작음 (LoRA 파라미터만 N배) | 큼 (FFN 전체 N배) |
Bench 로딩: MixtureLoRA 구조 보존
eulerforge bench에서 mixture_lora 체크포인트를 로드하면 MixtureLoRA 구조(base + router + N개 LoRA expert)를 그대로 재구성하여 routing 다양성을 보존합니다.
resolved_config.json에서 injection 파라미터(num_experts, top_k, lora_r 등)를 읽음build_mixture_lora_for_ffn_layers()로 base 모델에 MixtureLoRA 구조 주입- Attention LoRA만 병합 (
_merge_attention_lora_only()) — FFN MixtureLoRA 키는 보존 - MixtureLoRA 모델에 state_dict 로드 → router + N개 LoRA expert 구조로 추론
| 상태 | bench 동작 |
|---|---|
resolved_config.json 있음 |
MixtureLoRA 구조 재구성 → 구조 보존 추론 |
resolved_config.json 없음 |
Fallback: expert 평균 → dense 모델 (경고 출력) |
참고:
resolved_config.json이 없는 기존 체크포인트는 fallback으로 expert delta 평균 → dense 모델로 변환됩니다. 최신 훈련에서는 항상resolved_config.json이 저장됩니다.
7. 실행하기
기본 실행
eulerforge train --preset configs/presets/qwen3.5_0.8b_mixture_lora_sft.yml \
--set data.format=raw \
--set data.task=sft \
--set data.path=data/sft_10k_raw.jsonl \
--set data.max_length=512
설정 오버라이드
# 전문가 수와 top-k 변경
eulerforge train --preset configs/presets/qwen3.5_0.8b_mixture_lora_sft.yml \
--set data.format=raw \
--set data.task=sft \
--set data.path=data/sft_10k_raw.jsonl \
--set data.max_length=512 \
--set injection.num_experts=8 \
--set injection.top_k=2
설정 검증만
eulerforge train --preset configs/presets/qwen3.5_0.8b_mixture_lora_sft.yml \
--validate-only
프리플라이트 검사
eulerforge train --preset configs/presets/qwen3.5_0.8b_mixture_lora_sft.yml \
--preflight
8. 디버깅 및 트러블슈팅
| 증상 | 원인 | 해결 |
|---|---|---|
| "router_z_loss_coef is required" | moe 섹션 누락 또는 불완전 |
moe 섹션 전체 추가 |
| "load_balance is required" | moe.load_balance 누락 |
load_balance.type과 aux_loss_coef 추가 |
| "top_k cannot be larger than num_experts" | top_k > num_experts |
top_k <= num_experts로 수정 |
| 특정 전문가만 선택됨 (라우팅 붕괴) | aux_loss_coef가 너무 작음 |
aux_loss_coef를 0.01~0.1로 증가 |
| "router never in any phase" 경고 | 어떤 페이즈에도 router 그룹 없음 |
페이즈 0에 router 추가 |
| Phase 0에서 val_loss 변화 미미 | LoRA 전문가 출력이 작음 (정상) | Phase 0는 router 웜업 구간. Phase 1에서 본격적인 loss 하락 발생 |
| OOM | 전문가 수가 많아 메모리 부족 | num_experts 감소, lora_r 감소, model.load_precision.mode: int4 |
| bench에서 "누락된 키 N개" / "예기치 않은 키" | MoE 체크포인트를 dense LoRA로 병합 시도 | lora_info.json에 strategy 필드 확인. 최신 버전에서는 MixtureLoRA 구조 보존 자동 재구성 |
다음 단계
- FFN 전체를 MoE로 변환하려면 → MoE Expert LoRA 튜토리얼
- Mixtral 같은 네이티브 MoE 파인튜닝 → Native MoE Expert LoRA 튜토리얼
- DPO 훈련을 원한다면 → DPO 훈련 가이드