> EulerForge > 튜토리얼 > 2. Mixture-of-LoRAs

2. Mixture-of-LoRAs

개요

mixture_lora는 FFN의 각 Linear 레이어를 여러 개의 LoRA 전문가(expert) + 라우터로 교체하는 전략입니다. 토큰마다 라우터가 top-k개의 LoRA 전문가를 선택하여 가중합을 계산합니다. dense_lora가 "하나의 LoRA"라면, mixture_lora는 "E개의 LoRA + 선택 메커니즘"입니다.


사전 요구 사항


1. Where: 어디에 주입되는가?

dense_lora와 동일한 위치에 주입됩니다. 차이는 무엇으로 교체하는가입니다.

탐색 과정

  1. BackboneAdapter.find_transformer_layers(model) — 트랜스포머 블록 탐색
  2. 각 블록 내에서 target_keywords에 매칭되는 FFN nn.Linear 탐색
  3. DenseLoRAInjection 클래스가 build_mixture_lora_for_ffn_layers() 호출
  4. (선택) 어텐션 프로젝션에는 일반 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.LinearMixtureLoRALinear로 교체됩니다.

변환 과정

변환 전: 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_lora2페이즈 스케줄을 사용합니다. 라우터를 먼저 웜업한 후 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페이즈인가?

  1. 페이즈 0 (라우터 웜업): 라우터가 안정적인 전문가 선택 패턴을 먼저 학습합니다. 각 LoRA expert의 lora_Bstd=0.01의 small-random-init으로 초기화되어, 서로 다른 출력을 생성합니다 (symmetry-breaking). 이를 통해 라우터가 전문가 간 차이를 감지하고, 어떤 전문가를 어떤 입력에 할당할지 의미 있는 gradient를 받을 수 있습니다.

  2. 페이즈 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 다양성을 보존합니다.

  1. resolved_config.json에서 injection 파라미터(num_experts, top_k, lora_r 등)를 읽음
  2. build_mixture_lora_for_ffn_layers()로 base 모델에 MixtureLoRA 구조 주입
  3. Attention LoRA만 병합 (_merge_attention_lora_only()) — FFN MixtureLoRA 키는 보존
  4. 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.typeaux_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.jsonstrategy 필드 확인. 최신 버전에서는 MixtureLoRA 구조 보존 자동 재구성

다음 단계