contilab 설계 스펙
- 작성일: 2026-10-22
- 상태: 리뷰 대기
- 위치:
/home/user/contilab
1. 목적과 범위
1.1 목적
Continual Learning(CL) 접근법을 실증하기 위한 신규 프레임워크. 하나의 코드베이스에서 다음을 수행한다.
- 벤치마크: 비전·언어 CL 시나리오에서 메서드를 동일 조건으로 비교
- 학습: 대표 CL 메서드(정규화·리플레이·파라미터 격리)로 순차 학습
- 증류: (1) 망각 방지 기법으로서의 증류, (2) 태스크별 전문가를 단일 모델로 통합하는 사후 통합 파이프라인
1.2 설계 원칙
- 비전·LLM 공통 추상화: 동일한 Stream/Strategy/Evaluator 인터페이스
- 성능 우선: 학습 핫루프의 Python 오버헤드 제거, 다중 실행 병렬화(vmap), compile/bf16 자동 활용
- 풍부한 평가: 학계에서 사용되는 정량·정성 지표를 폭넓게 제공, 원자료 보존으로 사후 재계산 가능
- 공정 비교: 모든 메서드에 동일 루프·예산·평가 규약
- CPU→GPU 확장: 설정 프로파일 전환만으로 규모 변경
1.3 범위에 포함 (1차)
- 메서드: Naive, Joint, EWC(Online), LwF, ER, DER++, A-GEM, LoRA-per-task
- 사후 통합: Multi-teacher 증류, Task Arithmetic, TIES (전체 파라미터 및 LoRA ΔW)
- 시나리오: Task-IL, Class-IL, Domain-IL
- 지표: 6장 카탈로그 전체 (티어별 수집)
- 실행 백엔드: CPU / CUDA / MPS (자동 선택)
1.4 범위에서 제외 (후속)
- Teacher→Student 지속 증류 (인터페이스 확장 지점만 1차에 포함, 5.6절)
- SI, iCaRL, PackNet, L2P/CODA-Prompt, O-LoRA/InfLoRA, 생성 리플레이
- 분산(멀티 GPU) 학습
- Rust/C++ 네이티브 확장 (프로파일링으로 병목이 입증된 경우에만 후속 검토)
- TRACE 벤치마크 및 GPU 프로파일의 zero-shot 일반 능력 세트(후속)
1.5 성공 기준
smoke프로파일: 전 메서드·전 통합 기법이 60초 내 완주하고 전체 테스트 통과cpu프로파일: 이 클라우드 세션(4 CPU, 15GB RAM)에서 메서드×벤치마크×시드 매트릭스 완주, 리포트 생성- Split-MNIST Class-IL에서 알려진 정성 순서 재현: Naive < {EWC, LwF} < {ER, DER++} < Joint (ACC 기준, 시드 평균)
contilab bench로 vmap(E>1)·compile의 처리량 가속을 수치로 보고- 7장의 등가성·정합성 테스트 전부 통과
2. 실행 환경과 의존성
- Python 3.13, PyTorch(PyPI 최신 안정판), transformers, safetensors, datasets, numpy, scipy, scikit-learn(t-SNE/선형 probe 보조), matplotlib, pyyaml, psutil
- peft는 사용하지 않음 (자체 LoRA, 4.3절)
- 패키지 관리:
pyproject.toml,pip install -e .[dev](dev: pytest) - 현재 세션 확인 사항: PyPI·HuggingFace 접근 가능, download.pytorch.org 차단 → PyPI 휠 사용, gcc/g++ 존재(inductor CPU 컴파일 가능)
3. 아키텍처
3.1 패키지 구조
contilab/
core/ # 타입: Task, Stream, Batch, Scenario, RunContext
data/ # 벤치마크 빌더, 디바이스 텐서 캐시, 인덱스 배처
models/ # MLP, ReducedResNet18(GroupNorm), HF LM 분류기, LoRA
engine/ # 앙상블(E축) 상태, 학습 스텝, 컴파일, 평가기
strategies/ # CL 메서드 플러그인
consolidate/ # 전문가 학습·캐시, TA, TIES, Multi-teacher 증류
metrics/ # 지표 레지스트리, 수집기(probe), 지표 함수, 통계
report/ # 표·그림·마크다운/HTML 리포트
runner/ # 설정 로드, 매트릭스 전개, 스케줄러, 저장소, CLI
configs/ # 프로파일·실험 YAML
tests/
docs/
3.2 데이터 흐름
Config ─▶ Runner ─▶ Benchmark ─▶ Stream[Task]
│
├─ 순차 CL: Engine(Model, Strategy) ─ 태스크마다 ─▶ Collectors ─▶ RawLog
└─ 사후 통합: ExpertTrainer ─▶ Consolidator ─ 태스크마다 ─▶ Collectors ─▶ RawLog
RawLog ─▶ Metrics(순수 함수) ─▶ Results ─▶ Report
3.3 핵심 타입 (core)
Task:id,train: TensorSplit,test: TensorSplit,val: TensorSplit,classes: LongTensor(전역 클래스 ID)TensorSplit: 디바이스 상주 텐서x(이미지 또는 token ids),y, 텍스트일 경우attn_maskStream:tasks: list[Task],scenario: Literal["task", "class", "domain"],num_classesBatch:x, y, task_id, mask— 모든 필드는 선행 E축을 가짐 ([E, B, ...])RunContext: 현재 태스크 인덱스, 스텝 수, 디바이스, E, RNG, 로거 핸들
4. 데이터·모델·성능 코어
4.1 벤치마크
| 이름 | 태스크 구성 | 원본 | cpu 규모 | gpu 규모 |
|---|---|---|---|---|
synthetic | 가우시안 클러스터, 5태스크×2클래스 | 생성 | (smoke 전용) | — |
split_mnist | 5태스크×2클래스 | ylecun/mnist | 전체 | 전체 |
permuted_mnist | 10태스크, 고정 픽셀 순열, 10클래스 공유 | ylecun/mnist | 태스크당 학습 10k / 테스트 2k | 전체 |
split_cifar10 | 5태스크×2클래스 | uoft-cs/cifar10 | 전체 | 전체 |
split_cifar100 | 10태스크×10클래스 | uoft-cs/cifar100 | — | 전체 |
text5 | AG News(4) → Yelp(5) → Amazon(5) → DBpedia(14) → Yahoo(10) | fancyzhx/ag_news, Yelp/yelp_review_full, SetFit/amazon_reviews_multi_en, fancyzhx/dbpedia_14, community-datasets/yahoo_answers_topics | 태스크당 학습 2k / 테스트 500, 최대 길이 128 | 태스크당 학습 115k(원 설정) |
text5의 Yelp·Amazon은 같은 5단계 평점 라벨을 공유(de Masson d’Autume 등 2019 설정), 전체 33 클래스- 시나리오 지원: split 계열은 Task-IL/Class-IL, permuted는 Domain-IL, text5는 Class-IL(기본)과 Task-IL
- 검증 세트: 각 태스크 학습 데이터의 10%를 고정 시드로 분리 (TA/TIES λ 선택, 하이퍼파라미터 sweep용)
- 태스크 순서: 기본 고정,
task_order_seed지정 시 셔플
4.2 데이터 파이프라인 (속도)
- 최초 실행 시 다운로드 → 전처리(정규화·토크나이즈·패딩) →
~/.cache/contilab/<benchmark>/<hash>.safetensors로 저장. 해시는 전처리 설정 + 토크나이저 이름 기준 - 실행 시 전체 분할을 디바이스 텐서로 적재.
torch.utils.data.DataLoader미사용 - 배칭: 에폭마다 멤버별 순열
perm: [E, N]을 생성하고x[perm[:, i:i+B]]로 gather →[E, B, ...] - 데이터 증강은 1차 범위에서 사용하지 않음 (teacher 로짓 캐시 전제와 일관)
- 텍스트: 태스크 내 길이 버킷 정렬은 사용하지 않고 고정 길이 패딩 (E축 텐서 형태 고정 → compile 재사용)
4.3 모델
MLP(in, hidden=400, depth=2, out)— MNIST 계열 표준ReducedResNet18(nf=20, norm=GroupNorm)— CIFAR 계열. BatchNorm은 vmap과 running stats 문제로 사용하지 않음HFClassifier(model_name, pooling="last_token")— 텍스트. 기본EleutherAI/pythia-70m(cpu), 대안HuggingFaceTB/SmolLM2-135M, gpu 기본Qwen/Qwen2.5-1.5B- 분류 헤드: 모든 모델은 전체 클래스 수의 단일 선형 헤드. 시나리오별 로짓 마스크(4.5절)
- 자체 LoRA:
LoRALinear(base, r, alpha)가 대상nn.Linear를 감쌈. 어댑터 집합을dict[task_id, (A, B)]로 보관, 활성 어댑터 선택,delta_weight(task_id) = (alpha/r)·B@A제공. 대상 모듈은 설정(lora.targets). 기본: 텍스트는 어텐션 q/v 프로젝션, 비전은 헤드를 제외한 모든nn.Linear/nn.Conv2d.LoRAConv2d는 A: [r, in·k·k], B: [out, r]로 ΔW = B@A를 커널 형태로 reshape해W + ΔW로 합성곱 - 특징 추출 훅: 모든 모델은
features(x) -> [B, D](헤드 직전 표현)를 제공 — probe·CKA·rank 지표용
4.4 앙상블 축 E (성능 코어)
- 정의: E는 동시에 학습되는 독립 실행 수. 순차 CL에서는 시드, 전문가 학습에서는 (태스크 × 시드)
- 파라미터:
torch.func.stack_module_state로[E, ...]스택된 leaf 텐서 dict. 모델 계산은vmap(functional_call(base_model, ·)) - 손실:
loss_total = vmap(member_loss)(params, state, batch).sum()→ 멤버 간 그래디언트가 섞이지 않음 - 옵티마이저: 원소별 연산 옵티마이저(SGD, AdamW)를 스택 텐서에 그대로 사용 → 멤버 독립성 유지. 그래디언트 노름 클리핑은 멤버별(
[E, -1]기준)로 직접 구현 - 멤버별 상태: 데이터 순열, 리플레이 버퍼, Fisher, 구 파라미터 모두
[E, ...] - E=1 fast path: vmap을 생략하고 일반 모듈 호출 (LLM 기본)
- compile:
engine.compile ∈ {auto, on, off}.auto는 첫 스텝에서 컴파일을 시도하고 실패 시 eager 폴백 후 사유를 환경 정보에 기록 - 정밀도:
engine.precision ∈ {auto, fp32, bf16}.auto는 CUDA bf16 지원 또는 CPU AMX/AVX512-BF16 감지 시 bf16 autocast, 아니면 fp32 - 스레드:
torch.set_num_threads를 스케줄러가 프로세스별로 지정 - 처리량 계측: 매 실행에서 samples/s, 스텝 시간, 피크 메모리를 기록
4.5 시나리오별 로짓 마스킹
- Task-IL: 학습·평가 모두 해당 태스크 클래스만 허용
- Class-IL: 학습 시 현재까지 본 클래스 허용(미래 클래스 마스크), 평가 시 지금까지 본 전체 클래스
- Domain-IL: 공유 클래스 전체
- 마스크는
[num_classes]불리언 텐서를 로짓에masked_fill(-inf)로 적용 (멀티헤드 대비 커널 호출 1회)
5. 학습 메서드와 통합 파이프라인
5.1 Strategy 인터페이스
class Strategy:
vmappable: bool = True
# 호스트 측 (컴파일 대상 아님)
def before_task(self, ctx: RunContext, task: Task) -> None: ...
def replay_batch(self, ctx: RunContext) -> Batch | None: ... # [E, B] 인덱스 기반
def after_step(self, ctx: RunContext, batch: Batch, out: StepOutput) -> None: ...
def after_task(self, ctx: RunContext, task: Task) -> None: ...
# 핫패스 (멤버 단위로 작성, 엔진이 vmap·compile)
def loss(self, params, state, batch, replay, logits_fn) -> Tensor: ...
def transform_grad(self, grads, ref_grads) -> dict: return grads
def state(self) -> dict[str, Tensor]: ... # [E, ...] 스택 상태
5.2 엔진 학습 스텝
replay = strategy.replay_batch(ctx)- 핫패스 함수(컴파일 단위): 멤버별
loss계산 →torch.func.grad로 그래디언트 → 필요 시 버퍼 배치의 참조 그래디언트 계산 →transform_grad - 옵티마이저 스텝
strategy.after_step(버퍼 갱신 등), NaN 검사, 수집기 호출(연속 평가 주기 도달 시)
5.3 메서드 구현 규약
| 메서드 | 구현 | 기본 하이퍼파라미터 |
|---|---|---|
| Naive | CE | — |
| Joint | 스트림 변환: 모든 태스크를 하나의 태스크로 합친 뒤 Naive. Intransigence 기준값 a*_j도 제공 | — |
| EWC (Online) | after_task에서 검증 제외 학습 샘플 N개에 대해 vmap(grad) per-sample 그래디언트로 대각 Fisher, F ← γF + F_new. 손실 CE + λ/2·ΣF(θ−θ*)² | λ=100(비전) / λ=10(텍스트), γ=1.0, N=1024 |
| LwF | after_task에서 파라미터 스냅샷 고정. 같은 스텝에서 no-grad forward, 이전 클래스 로짓에 KD | T=2, α=1 |
| ER | 멤버별 reservoir 버퍼(데이터셋 전역 인덱스만 저장). 현재 배치 + 같은 크기 리플레이 배치 결합 CE | M=500(cpu 비전)/200(text5) |
| DER++ | 버퍼에 인덱스 + 삽입 시점 로짓. CE + α·MSE(z, z_buf) + β·CE(y_buf2) | α=0.1, β=0.5, M 동일 |
| A-GEM | 스텝 내 참조 그래디언트(버퍼 배치). g·g_ref<0이면 g − (g·g_ref/‖g_ref‖²)·g_ref (멤버별) | M 동일, 참조 배치 256 |
| LoRA-per-task | 백본 고정, before_task에서 새 어댑터 추가·활성화. 분류 헤드는 모든 태스크에서 공유·학습(4.5절 마스킹으로 Task-IL에서는 현재 태스크 행만 갱신). Task-IL 평가는 태스크 ID로 어댑터 선택. Class-IL/Domain-IL 평가는 지금까지의 어댑터를 ΔW 단위 TA로 병합한 모델 사용(λ는 5.5절과 같은 방식으로 본 태스크 검증 세트에서 선택) | r=8, α=16 |
- 버퍼는 서로 다른 태스크의 데이터셋을 가리키므로 인덱스는
(task_id, sample_idx)쌍으로 저장 - 공정성 규약: 동일 옵티마이저(기본 SGD lr=0.03 비전 / AdamW lr=1e-4 텍스트, LoRA는 1e-3), 동일 에폭(cpu: split_mnist 1, permuted_mnist 1, split_cifar10 3, text5 1), 동일 배치(비전 32, 텍스트 16), 동일 버퍼 예산
- 하이퍼파라미터는
sweep블록으로 그리드 실행 가능, 선택은 검증 세트 기준
5.4 사후 통합: 전문가 학습
- 모든 전문가는 공통 초기값 θ₀(사전학습 가중치 또는
init_seed고정 랜덤)에서 시작 - 전문가 i는 태스크 i만 학습 (전체 파라미터 또는 LoRA 어댑터)
- E축 = (태스크 × 시드)로 묶어 한 번의 vmap 실행으로 학습 (텍스트/LLM은 E=1 순차)
- 전문가 캐시:
~/.cache/contilab/experts/<config_hash>/. 해시 = 벤치마크 + 모델 + 학습 설정 + 시드
5.5 통합 기법 (Consolidator)
class Consolidator:
def consolidate(self, base: Params, experts: list[Expert], ctx: RunContext) -> Params: ...
매 태스크 t 후 experts[:t+1]로 호출 → 정확도 행렬과 모든 지표가 순차 CL과 동일하게 정의됨.
| 기법 | 구현 |
|---|---|
| Task Arithmetic | τ_i = θ_i − θ₀, θ = θ₀ + λ·Σ τ_i. λ ∈ {0.1, 0.2, …, 1.0}에서 검증 ACC 최대값 선택 |
| TIES | τ_i의 상위 k% 크기만 유지(k=20 기본) → 원소별 부호 선출(합의 부호 = Σ τ_i의 부호) → 합의 부호와 일치하는 값만 평균 → θ₀ + λ·τ, λ는 TA와 같은 방식 선택 |
| Multi-teacher 증류 | 학생 초기값: θ₀ 또는 TA/TIES 병합 결과(init: base|ta|ties). 손실 KL(T=2), 라우팅 routed(샘플 소속 태스크 teacher) 또는 entropy(teacher별 예측 엔트로피의 softmax(−H) 가중 앙상블). 전이 데이터 3단계: full(전체 태스크 데이터), buffer(태스크당 M/t 샘플, 리플레이와 같은 총 예산 M), proxy(비전: 다른 데이터셋의 무라벨 이미지 — MNIST 계열은 CIFAR-10 그레이스케일 28×28 축소본, split_cifar10은 CIFAR-100, split_cifar100은 CIFAR-10; 텍스트: Salesforce/wikitext 문장) |
| LoRA 병합 | A·B 개별 병합 금지. ΔW_i = (α/r)·B_i A_i 단위로 TA/TIES 적용 후 백본에 가산 |
- Class-IL 헤드: 백본은 병합, 헤드는 각 전문가에서 자기 태스크 클래스 행만 가져와 결합. 로짓 스케일 불일치는 리포트에 한계로 명시하며, 증류가 이를 보정하는지를 비교 축으로 사용
- teacher 로짓 캐시: 증강이 없으므로 전이 데이터에 대한 teacher 로짓을 1회 계산해 저장, 증류 에폭 동안 teacher forward 없음
- 비용 산정: 통합 기법의 학습 비용 = 전문가 학습 총합 + 통합 비용. 저장량 = 보관 전문가/어댑터 바이트 포함
5.6 확장 지점: Teacher→Student 지속 증류 (후속)
Consolidator.__init__(student_factory=None): teacher와 다른 아키텍처의 학생 생성 함수를 받을 수 있음Consolidator.on_new_expert(expert, ctx)훅: 순차적으로 도착하는 teacher를 처리하는 구현을 위해 1차에서 기본 no-op으로 정의- v0.2 구현:
consolidate:continual_distill: 태스크마다 teacher 1개가 도착하고 사용 후 폐기된다. 학생 학습은 KD와 리플레이 버퍼 위 자기 증류를 결합한다.- 학생 아키텍처는
student_factory인자 대신method.params.student(ModelConfig 덮어쓰기)로 지정한다.
6. 평가 지표 체계
6.1 원칙
- Raw-log 우선: 학습 중에는 원자료만 수집, 지표는
RawLog → 값의 순수 함수 - 지표 등록:
@metric(name, requires=[raw_keys], tier="core"|"extended"|"full") - 러너는 활성 티어의 지표들이
requires로 선언한 원자료만 수집 contilab metrics runs/<exp> --add <name>: 원자료가 있으면 재실행 없이 계산, 없으면 필요한 원자료와 티어를 안내
6.2 원자료 (RawLog 키)
| 키 | 내용 | 수집 시점 | 티어 | ||
|---|---|---|---|---|---|
acc_matrix | R[i, j]: 태스크 i 학습 후 태스크 j 테스트 정확도 | 태스크 종료 | core | ||
init_acc | b_j: 학습 전(θ₀) 태스크 j 정확도 | 시작 시 | core | ||
cost | 시간, samples/s, 피크 메모리, 학습 파라미터 수, 버퍼 바이트, 모델 바이트, FLOPs 추정 | 상시 | core | ||
pred_hist | 태스크 종료 시 테스트 샘플별 예측 클래스·정답·confidence | 태스크 종료 | core | ||
stream_acc | 매 ρ 스텝(기본 ρ=50)마다 본 태스크 전체의 서브셋(태스크당 200) 정확도 | 연속 | extended | ||
lca_curve | 각 태스크 첫 β(기본 10) 배치에서의 현재 태스크 서브셋 정확도 | 태스크 시작부 | extended | ||
features | 고정 probe 세트(태스크당 256 샘플)의 features(x) | 태스크 종료 | extended | ||
activations | probe 세트의 은닉층별 활성화 통계(뉴런별 평균 | h | ) | 태스크 종료 | extended |
param_snapshots | 태스크 종료 시 파라미터(멤버 0만, safetensors) | 태스크 종료 | full | ||
task_grads | 태스크 종료 시 각 태스크 검증 배치 손실의 그래디언트(평탄화, 멤버 0) | 태스크 종료 | full | ||
recovery | 최종 모델 복사본을 태스크 j의 n=50 샘플로 K=20 스텝 재학습한 후 정확도 | 실행 종료 | full | ||
general_ppl | (텍스트) Salesforce/wikitext 테스트 서브셋(cpu 200문장) perplexity, θ₀ 및 각 태스크 종료 | 태스크 종료 | extended |
6.3 정량 지표 정의
표기: T = 태스크 수, R[i, j] (0-index), b_j = init_acc.
종합·안정성·가소성 (core)
- ACC = (1/T) Σ_j R[T−1, j]
- AIA (Average Incremental Accuracy) = (1/T) Σ_i [(1/(i+1)) Σ_{j≤i} R[i, j]]
- BWT = (1/(T−1)) Σ_{j<T−1} (R[T−1, j] − R[j, j])
- FWT = (1/(T−1)) Σ_{j≥1} (R[j−1, j] − b_j)
- Forgetting: f_j = max_{l∈[j, T−2]} R[l, j] − R[T−1, j], F = mean_{j<T−1} f_j
- Relative Forgetting: f_j / max_{l∈[j, T−2]} R[l, j] 의 평균
- Learning Accuracy LA = (1/T) Σ_j R[j, j]
- Intransigence I = (1/T) Σ_j (a_j − R[j, j]), a_j = 같은 실험의 Joint 실행에서 태스크 j 정확도 (Joint 실행이 없으면 미계산으로 표시)
연속 평가 (extended, De Lange 2023 / Caccia 2022)
- A(k, n): 스텝 n에서 태스크 k 정확도 (
stream_acc) - min-ACC = 태스크 k 학습 종료 이후 모든 n에서 min A(k, n)의 평균 (k < T−1)
- WC-ACC = (1/T)·A(T−1, 끝) + (1 − 1/T)·min-ACC
- AAA (Average Anytime Accuracy) = 모든 평가 시점에서 (본 태스크 평균 정확도)의 평균
- Stability Gap = 태스크 k 종료 시 정확도 − 다음 태스크 학습 중 태스크 k의 최소 정확도, k에 대해 평균
- WF(Δ) (Windowed Forgetting) = 길이 Δ 스텝 윈도우 내 최대 정확도 낙폭, 태스크 평균 (기본 Δ = 태스크 길이의 10%)
- LCA_β = (1/(β+1)) Σ_{b=0..β} Z_b, Z_b = 태스크 평균 배치 b 시점 현재 태스크 정확도 (Chaudhry 2019)
표현 수준 (extended)
- Probe 정확도 P[i, j]: 태스크 i 종료 시 고정 특징으로 태스크 j 선형 분류기(로지스틱 회귀, probe 세트 80/20 분할) 정확도
- 표현 망각 = P[j, j] − P[T−1, j], 헤드 망각 = (R[j, j] − R[T−1, j]) − 표현 망각, 각 태스크 평균 (Davari 2022)
- CKA[j]: 태스크 j probe 특징의 (태스크 j 종료 시 vs 최종) linear CKA, 및 태스크 종료 시점 간 CKA 행렬 (Kornblith 2019)
- 특징 드리프트: 같은 샘플 특징의 정규화 L2 이동량 평균
가소성 손실 진단 (extended; 반드시 최적화 기반 지표와 함께 보고)
-
Dormant 비율: 뉴런 점수 s_i = E h_i / mean_k E h_k ≤ τ 인 비율 (τ=0.025, Sokar 2023) - Effective rank: 특징 행렬 특이값 정규화 분포의 엔트로피 지수 (Roy & Vetterli 2007)
- srank_δ: Σ_{i≤k} σ_i / Σ σ ≥ 1 − δ 를 만족하는 최소 k (δ=0.01, Kumar 2021)
- 가중치 노름: 레이어별 Frobenius 노름 추이
- 짝 지표: LA, LCA_β (신규 태스크 학습 능력) — 리포트에서 같은 표에 배치
편향·신뢰도 (core)
- Task-recency bias: Class-IL에서 이전 태스크 샘플 중 가장 최근 태스크 클래스로 예측된 비율 (Wu 2019, Masana 2022)
- 태스크별 예측 분포: 정답 태스크 × 예측 태스크 행렬
- ECE: 15 bin, 태스크별 및 전체
복구성 (full)
- Recovery_j = (재학습 후 정확도 − R[T−1, j]) / (R[j, j] − R[T−1, j]), 분모 ≤ 0.01이면 제외
- 높은 Recovery는 “피상적 망각”(지식 보존, 정렬 손상)의 증거로 해석 (Spurious Forgetting, ICLR 2025)
손실 지형·간섭 (full)
- Loss barrier_k = max_α L_k((1−α)θ_k + αθ_{k+1}) − [(1−α)L_k(θ_k) + αL_k(θ_{k+1})], α ∈ {0, 0.1, …, 1}, L_k는 태스크 k 검증 손실 (Mirzadeh 2020)
- 그래디언트 간섭 행렬:
task_grads간 코사인 유사도
LLM 전용 (extended)
- General retention Δ = general_ppl(최종) − general_ppl(θ₀) 및 태스크별 추이 (TRACE의 General Ability Delta를 cpu 규모에서 perplexity로 대체)
효율 (core)
- 총 학습 시간, samples/s, 피크 메모리(CPU: psutil RSS 최대값, CUDA:
max_memory_allocated), 학습 파라미터 수, 버퍼 바이트, 모델 크기 증가(어댑터/전문가 포함), FLOPs(torch.utils.flop_counter.FlopCounterMode로 1스텝 측정 × 스텝 수)
6.4 통계
- 시드별 값 → 평균 ± 표준편차, 부트스트랩 95% CI(10,000회 재표본)
- 기준 메서드(설정, 기본 Naive) 대비 paired 검정: 시드 수 ≥ 6이면 Wilcoxon signed-rank, 미만이면 정확 permutation test
- 벤치마크 간: 지표별 평균 순위, Friedman 검정, Nemenyi critical difference (Demšar 2006)
- 시드 수가 적을 때(< 5) 리포트에 검정력 한계 경고 표시
6.5 정성 산출물
정확도 행렬 히트맵, 연속 평가 곡선(stability gap 구간 강조), 혼동 행렬 및 정답 태스크×예측 태스크 행렬, CKA 히트맵, 레이어별 가중치 드리프트 막대, 그래디언트 간섭 히트맵, probe 특징 PCA 및 t-SNE, 손실 보간 곡선, CD 다이어그램, 처리량 비교 막대(bench).
7. 러너·저장·리포트·에러 처리
7.1 CLI
| 명령 | 기능 | |—|—| | contilab run <cfg.yaml> [key=val ...] | 실험(매트릭스) 실행 | | contilab report <runs/exp> | report.md + report.html(그림 PNG 임베드) 생성 | | contilab metrics <runs/exp> --add <name> [--tier ...] | 지표 사후 계산 | | contilab bench [--model ...] [--E 1,2,4,8] | eager / compile / vmap(E) 처리량 비교 | | contilab data prepare <benchmark> | 데이터 사전 다운로드·캐시 |
7.2 설정
- YAML → dataclass 검증. 알 수 없는 키는 에러
- 프로파일:
configs/profiles/{smoke,cpu,gpu}.yaml가 데이터 규모·모델·에폭·티어 기본값을 정의, 실험 YAML이profile:로 참조 후 덮어씀 - 매트릭스:
matrix: {benchmark: [...], method: [...], seed: [...]}데카르트 곱.sweep:은 메서드 하이퍼파라미터 그리드 method에consolidate:ta,consolidate:ties,consolidate:distill지정 가능 (전이 데이터 단계는consolidate.transfer)
7.3 저장 구조
runs/<exp>/
experiment.yaml # 전개 전 원본
<run_id>/
config.yaml # 해석 완료 설정
env.json # 버전, 하드웨어, compile/precision 결정과 사유, git 해시
status.json # pending|running|done|failed|diverged + traceback
raw/*.jsonl, raw/*.safetensors
ckpt/task_<i>.safetensors
metrics.json
results.csv # 전 run × 지표
report.md, report.html, figs/
run_id= 설정 해시.status=done이면 재실행 시 건너뜀 → 스윕 재시작 멱등- 중단 후 재시작 시 마지막 태스크 체크포인트부터 재개
7.4 스케줄러
- 시드만 다른 run들을 하나의 E축 실행으로 묶음 (
engine.max_E, 기본 8, 메모리 한도 초과 시 분할) - 묶인 실행은
workers × threads_per_worker ≤ 코어 수로 프로세스 병렬. 기본: 비전 2×2, 텍스트 1×4 - 결과는 멤버별 run 디렉터리에 분리 저장 (vmap 사용 여부와 무관하게 동일 구조)
7.5 에러 처리
- run 단위 격리: 예외 시
status=failed+ traceback, 스윕은 계속 - 발산: 손실 NaN/Inf 또는 손실이 초기값의 1e3배 초과 → 해당 멤버
diverged, 해당 멤버 그래디언트를 0으로 고정하고 나머지 멤버는 계속, 리포트에 표시 - compile 실패 → eager 폴백,
env.json에 사유 vmappable=False전략 또는 vmap 실패 → E=1 프로세스 병렬로 자동 폴백, 사유 기록- 데이터 다운로드 실패 → 캐시 경로와 수동 배치 방법을 포함한 에러 메시지
- 지표 계산 실패 → 해당 지표만
null과 사유, 나머지 지표는 계속
7.6 리포트
- 주 표: 벤치마크별 메서드 × {ACC, AIA, BWT, FWT, F, LA, min-ACC, WC-ACC, 시간, 메모리} (평균 ± 표준편차, 기준 대비 유의성 표시)
- 축별 표: 안정성 / 가소성 / 표현 / 가소성 손실 / 편향·신뢰도 / 복구성 / 효율
- 순위표와 CD 다이어그램, 6.5절 그림 전부
- 실행 환경 요약과 한계(시드 수, 서브샘플 규모, Class-IL 병합 헤드 스케일 문제)
8. 테스트
| 종류 | 내용 |
|---|---|
| 지표 단위 | 손으로 계산한 정확도 행렬·스트림 곡선·혼동 행렬로 6.3절 각 공식 검증, 경계 조건(T=1, Joint 부재) |
| 통계 단위 | 알려진 입력에 대한 permutation/Wilcoxon/Friedman 결과를 scipy 기준값과 비교 |
| 성능 코어 등가성 | vmap E=k 실행의 각 멤버 == 같은 시드의 E=1 개별 실행 (fp32, atol 1e-5, 몇 스텝), compile == eager |
| 전략 정합성 | EWC(λ=0) == Naive, ER(M=0) == Naive, DER++(α=β=0) == Naive, A-GEM에서 내적 ≥ 0이면 그래디언트 불변 |
| 통합 정합성 | 전문가 1개 TA(λ=1) == 전문가, TIES(k=100%, 전문가 1개, λ=1) == TA, LoRA ΔW 병합 모델 == 어댑터 활성 모델 출력 |
| 데이터 | 분할의 클래스 구성, 순열 고정성, 캐시 해시 재사용, 버퍼 인덱스 유효성 |
| 통합 | smoke 프로파일로 전 메서드·전 통합 기법·report·metrics 재계산이 60초 내 완주 |
| 실증 | cpu 프로파일 결과로 1.5절 성공 기준 3·4 확인 (자동 테스트가 아닌 실증 리포트) |
9. 위험과 대응
- vmap + compile 호환성: 일부 연산이 vmap/dynamo 미지원일 수 있음 → 7.5절 폴백,
bench로 실제 이득 확인 후 기본값 결정 - CPU 텍스트 실험 시간: Pythia-70M, 길이 128, 태스크당 2k 샘플 기준으로 설계. 실측이 예산을 넘으면
cpu프로파일의 텍스트 샘플 수를 줄이고 리포트에 명시 - 소형 LM의 낮은 절대 성능: 메서드 간 상대 비교가 목적임을 리포트에 명시
- 시드 수 부족에 따른 통계 검정력: 6.4절 경고 표시
- HF 데이터셋 경로 변경: 벤치마크 빌더가 데이터셋 ID를 설정으로 받음