> EulerForge > 튜토리얼 > 14. LoRA Handoff 스케줄링

14. LoRA Handoff 스케줄링

전제 조건: 튜토리얼 03 (FFN MoE Expert LoRA) 완료

연구/고급 기능 (Experimental): LoRA Handoff는 LoRA → base FFN 지식 전이를 위한 연구용 고급 기능입니다. 대부분의 경우 표준 3-Phase 훈련 (Handoff 없음)으로 충분합니다. 상세: checkpoint_lora_lifecycle.md §7

전략 제한: moe_expert_lora 전략에서만 사용 가능합니다.

훈련 타입 제한: DPO + Handoff 조합은 구조적으로 비호환입니다. LoRA가 freeze되면 policy = reference → 학습 불가. Handoff는 SFT 또는 ORPO에서만 사용하세요. dense_lora/mixture_lora에서는 base weight가 고정(freeze)되어 "지식 이전"이 성립하지 않습니다.


목표

moe_expert_lora 전략에서 LoRA를 점진적으로 줄여(fade) base FFN으로 지식을 이전하는 방법을 학습합니다.


1. 왜 LoRA Handoff인가?

moe_expert_lora 전략은 FFN을 MoE로 확장하고, 각 expert에 LoRA를 감쌉니다. 이 접근법은 효율적이지만, 최종 모델에 LoRA adapter가 남아 있어:

LoRA Handoff는 이 문제를 해결합니다:

  1. 초기에 LoRA로 빠르게 수렴 시작
  2. base_ffn unfreeze 후 점진적으로 LoRA 영향력 감소 (lora_scale: 1.0 → 0.0)
  3. 동시에 base_ffn LR을 올려 base weight가 LoRA의 역할을 흡수
  4. 최종적으로 LoRA 없는 순수 모델 획득

왜 moe_expert_lora만인가?

전략 base_ffn 학습 가능? Handoff 의미
dense_lora freeze (기본) scale=0 → 원본 base로 복귀. 이전 없음
mixture_lora freeze (기본) 동일. base 1개, 학습 안 됨
moe_expert_lora 각 expert 사본별 unfreeze 가능 scale↓ + base LR↑ → 지식 이전 성립

dense_lora/mixture_lora에서 lora_scale=0 + remove를 하면, "지식 이전"이 아니라 변화가 사라져 원본 base로 돌아가는 것입니다. 따라서 training.lora_handoffinjection.strategy=moe_expert_lora에서만 허용됩니다.


2. 기본 설정 예시

# configs/presets/qwen3.5_0.8b_moe_expert_lora_sft_handoff.yml 기반
device: cuda:0
backbone: qwen3
model_name: Qwen/Qwen3.5-0.8B-Base

injection:
  strategy: moe_expert_lora      # ← 필수: handoff는 이 전략에서만 동작
  lora_r: 48
  lora_alpha: 96
  lora_dropout: 0.05
  num_experts: 4
  top_k: 2
  target_keywords: [gate_proj, up_proj, down_proj]
  start_layer: 0
  num_layers: 0
  attn_lora:
    enabled: true
    keywords: [q_proj, v_proj]

moe:
  router_z_loss_coef: 0.001
  load_balance:
    type: aux_loss
    aux_loss_coef: 0.01
  router_dtype: float32

training:
  type: sft
  phases:
    - step: 0
      trainable: ["router"]              # Phase 0: 라우터 워밍업
    - step: 500
      trainable: ["lora", "attn_lora"]   # Phase 1: LoRA 학습
    - step: 4000
      trainable: ["lora", "attn_lora", "router", "base_ffn"]  # Phase 2: base_ffn unfreeze
      base_ffn_keywords: ["gate_proj", "up_proj", "down_proj"]
      # target_layers 생략 → injection.start_layer/num_layers에서 자동 추출
  lr: 1.0e-5
  weight_decay: 0.01
  warmup_steps: 200
  max_train_steps: 10000
  batch_size: 4
  grad_accum_steps: 4
  max_grad_norm: 1.0
  log_steps: 50
  save_steps: 2000
  val_steps: 1000

  # ── LoRA Handoff ──
  lora_handoff:
    expert_lora:
      start_step: 4000        # base_ffn unfreeze와 동시에 fade 시작
      duration_steps: 4000    # 4000 스텝에 걸쳐 점진적 감소
      end_scale: 0.0          # 완전 제거
      curve: cosine           # cosine 감쇠 (초반 완만, 후반 급격)
      end_action: freeze      # fade 완료 후 LoRA 파라미터 동결
    attn_lora:
      start_step: 5000
      duration_steps: 3000
      end_scale: 0.0
      curve: linear
      end_action: freeze
    base_ffn_ramp:
      start_step: 4000
      end_step: 8000
      start_multiplier: 1.0
      end_multiplier: 3.0
    export:
      remove_adapters_if_zero_scale: true

전제 조건 (Preconditions)

Validator가 자동으로 검사합니다:

조건 위반 시
P1: injection.strategy = moe_expert_lora ConfigValidationError
P2: training.phasesbase_ffn이 최소 1개 포함 ConfigValidationError
P3: lora_handoff.attn_lorainjection.attn_lora.enabled: true ConfigValidationError

3. 훈련 로그에서 Handoff 확인

Handoff가 활성화되면 훈련 로그에 다음 이벤트가 자동 출력됩니다:

[Train] LoRA Handoff Scheduler enabled: ['expert_lora', 'attn_lora', 'base_ffn_ramp', 'export']
[Handoff] Schedule: expert_lora(step 4000→6000, curve=cosine, end_scale=0.0, end_action=freeze), attn_lora(step 4000→6000, curve=linear, ...), base_ffn_ramp(step 2000→4000, LR ×1.0→×3.0)
...
[Handoff] base_ffn_ramp 시작 at step 2000 (LR ×1.0→×3.0)
...
[Handoff] base_ffn_ramp 완료 at step 4000 (LR ×3.0)
[Handoff] expert_lora fade 시작 at step 4000 (curve=cosine, end_scale=0.00, duration=2000 steps)
[Handoff] attn_lora fade 시작 at step 4000 (curve=linear, end_scale=0.00, duration=2000 steps)
...
[Phase2] Step 480/600 ... | Handoff[expert_lora=0.50, attn_lora=0.50, ffn_lr_mult=3.00]
...
[Handoff] expert_lora fade 완료 at step 6000 (scale=0.0000)
[Handoff] expert_lora frozen at step 6000 (scale=0.0000)
[Handoff] attn_lora fade 완료 at step 6000 (scale=0.0000)
[Handoff] attn_lora frozen at step 6000 (scale=0.0000)

4. Fade 곡선 이해

Linear Fade

lora_scale
1.0 ┤████████████
    │            ╲
0.5 ┤             ╲
    │              ╲
0.0 ┤               ████████
    └──┬──────┬──────┬──────→ step
       start  mid    end

균일한 속도로 감소. 단순하고 예측 가능.

Cosine Fade

lora_scale
1.0 ┤████████████╲
    │             ╲
0.5 ┤              ╲
    │               ╲
0.0 ┤                ╲██████
    └──┬──────┬──────┬──────→ step
       start  mid    end

초반 완만 → 후반 급격. LoRA 의존도를 부드럽게 줄이고 싶을 때 권장.


5. base_ffn_ramp (LR 보상)

LoRA가 줄어들면서 base FFN이 그 역할을 대신해야 합니다. LoRA 기여분이 줄어드는 동안 base FFN이 이를 빠르게 흡수하지 못하면 loss spike가 발생합니다. base_ffn_ramp는 이 구간의 base_ffn LR을 점진적으로 올려 지식 전이를 가속합니다.

구간 설정 원칙

핵심: ramp 구간 = expert_lora fade 구간

expert_lora:   start_step=4000, duration_steps=4000  → fade 구간 4000~8000
base_ffn_ramp: start_step=4000, end_step=8000        → 동일 구간에 정렬

타이밍 시각화 (총 10k step):

Step 0───500───4000───────────8000──────10000
     │    │     │               │          │
     │    │     ├─ expert_lora fade (1.0→0.0, cosine)
     │    │     ├─ base_ffn_ramp (LR ×1.0→×3.0)
     │    │     │         │
     Phase0  Phase1    Phase2   ├─ Handoff 완료
     router  LoRA    base_ffn   └─ 남은 2k step: base만으로 안정화
             훈련    unfreeze

안티패턴

설정 문제
ramp가 fade보다 일찍 끝남 (예: 4k~6k) fade 후반부에서 LR 보상 없이 LoRA가 계속 줄어듦 → loss spike
ramp가 fade보다 늦게 끝남 (예: 4k~10k) fade 완료 이후에도 LR이 계속 올라감 → 발산 위험
end_multiplier ×5 이상 gradient explosion 위험 (권장: ×2~×3)

YAML 예시

training:
  phases:
    - step: 0
      trainable: ["router"]
    - step: 500
      trainable: ["lora", "attn_lora"]
    - step: 4000
      trainable: ["lora", "attn_lora", "router", "base_ffn"]  # ← base_ffn 필수
      base_ffn_keywords: ["gate_proj", "up_proj", "down_proj"]

  lora_handoff:
    expert_lora:
      start_step: 4000
      duration_steps: 4000       # fade 구간: 4000~8000
      end_scale: 0.0
      curve: cosine
      end_action: freeze
    base_ffn_ramp:
      start_step: 4000           # expert_lora fade와 동시에 시작
      end_step: 8000             # expert_lora fade 완료와 동시에 종료
      start_multiplier: 1.0      # 기존 LR 유지
      end_multiplier: 3.0        # fade 구간 동안 3배까지 점진 증가

주의: base_ffn_ramp를 사용하려면 training.phases에서 base_ffn 그룹이 활성화되어 있어야 합니다 (P2).

Validator 자동 검증

validate_config()가 다음을 자동으로 검사합니다:

검사 수준 설명
fade start < base_ffn phase 에러 base_ffn이 아직 동결 상태에서 fade가 시작됨
fade start ≥ max_train_steps 에러 fade가 실행되지 않음
ramp가 fade보다 일찍 끝남 경고 fade 후반부에 LR 보상 없음 → loss spike 위험
ramp가 fade보다 늦게 끝남 경고 불필요한 LR 부스트 → 발산 위험
fade/ramp가 max_train_steps 초과 경고 훈련 내에 완료되지 않음

잘못된 설정 시 훈련 시작 전 즉시 에러 또는 경고가 출력됩니다.


6. Export 옵션

remove_adapters_if_zero_scale: true

훈련 완료 시 lora_scale=0.0인 LoRA 모듈을 plain nn.Linear로 교체합니다. 결과: LoRA 파라미터가 없는 순수 모델 저장.

merge_adapters_on_export: true

LoRA delta를 base weight에 merge한 후 plain nn.Linear로 교체합니다. end_scale > 0일 때 유용 (부분적 LoRA를 base에 흡수).

export:
  merge_adapters_on_export: true   # LoRA를 base에 merge

7. 체크포인트 저장 구조와 Bench 호환성

Handoff 전후 체크포인트 비교

항목 handoff 없음 (기본) handoff (end_scale=0)
expert FFN N개 (각각 base_layer + lora_A/B) N개 (LoRA 병합 후 weight만)
router 있음 있음
LoRA 파라미터 있음 없음 (병합 후 제거)
체크포인트 크기 큼 (LoRA 포함) 작음 (LoRA 제거)
handoff 전:
├── experts.0.gate_proj.base_layer.weight + lora_A + lora_B
├── experts.1.gate_proj.base_layer.weight + lora_A + lora_B
├── ...
└── router.weight

handoff 후 (end_scale=0, remove_adapters_if_zero_scale=true):
├── experts.0.gate_proj.weight    ← LoRA가 base에 병합됨
├── experts.1.gate_proj.weight
├── ...
└── router.weight                 ← 라우터 유지

핵심: 두 경우 모두 N개 expert + router 구조는 그대로 유지됩니다. Handoff는 LoRA 제거만 수행하며, MoE 구조를 축소하지 않습니다.

Bench 로딩

LoRA Handoff 체크포인트는 자동으로 bench에서 인식됩니다:

  1. LocalHFClientresolved_config.json에서 injection 설정 읽기
  2. base HF 모델에 replace_ffn_with_moe_inplace()로 MoE 구조 재구성
  3. Handoff 체크포인트: LoRA 키 없음 → MoE 모델에 직접 로드
  4. 비-Handoff 체크포인트: expert 내 LoRA 병합 후 로드
  5. MoE 구조로 추론 (학습된 expert routing 패턴 보존)

별도 설정 불필요:

eulerforge bench \
  --preset configs/bench/sft_target_only.yml \
  --target-output-dir outputs/run_handoff/

8. 부분 Fade (end_scale > 0)

완전 제거 대신 LoRA 영향력을 일부만 남기고 싶을 때:

lora_handoff:
  expert_lora:
    start_step: 4000
    duration_steps: 4000
    end_scale: 0.3         # 30% 유지
    end_action: keep       # 계속 학습 가능

이 경우 export.merge_adapters_on_export: true로 부분 merge 가능.


9. 검증

설정 검증은 validate_config()에서 자동으로 수행됩니다 (규칙 H1~H7). 주요 검증 항목:

# 검증만 수행
eulerforge train --preset configs/presets/qwen3.5_0.8b_moe_expert_lora_sft_handoff.yml --validate-only

상세: lora_handoff_spec.md


10. 프리셋

프리셋 모델 설명
qwen3.5_0.8b_moe_expert_lora_sft_handoff.yml Qwen3.5-0.8B 3-phase + handoff
llama3_1b_moe_expert_lora_sft_handoff.yml Llama-3.2-1B 3-phase + handoff