제8부 운영·LLMOps
8.3 파인튜닝 운영·모델 라이프사이클
파인튜닝을 처음 끝낸 팀의 머릿속 그림은 대개 이렇다. 데이터를 모아 한 번 실행했더니 우리 도메인에 맞는 모델이 나왔고, 이제 이것을 서빙에 추가하면 일이 끝난다. 이 그림이 틀렸다는 것을 보통은 반년가량 뒤에 깨닫는다 — 사용자 질문의 결이 슬그머니 바뀌어 있고, 베이스 모델은 새 버전이 나와 옛 버전이 폐기 예고되고, 어느샌가 관리 대상 어댑터가 수십 개로 불어나 있고, 규제 담당이 "이 모델 무슨 데이터로 만들었냐"고 묻는데 아무도 답을 못 한다. 파인튜닝된 모델은 완성된 산출물이 아니라 코드처럼 버전을 갖고, 노후화되고, 유지보수 부담을 남기는 자산이다.
여기에 파인튜닝을 둘러싼 가장 끈질긴 오해가 하나 더 추가된다. "우리 문서를 파인튜닝으로 모델에 넣자"는 유혹이다. SFT(3.2)와 PEFT(3.8)에서 반복해 봤듯, 파인튜닝은 행동·형식·톤·도메인 문체를 각인하는 데 강하고 사실을 암기시키거나 최신 지식을 주입하는 데는 약하며 오히려 환각을 늘린다. 지식은 RAG(6.3)가 담당하고 파인튜닝은 "어떻게 말하고 행동하나"를 담당한다 — 이 경계를 흐리면 지식이 바뀔 때마다 다시 튜닝해야 하는 끝없는 재학습 지옥에 빠진다. 이 두 사실, 곧 "한 번 튜닝하면 끝"이 아니라는 것과 "파인튜닝은 지식 주입이 아니라는 것"이 이 장 전체의 저류다.
이 장은 파인튜닝을 어떻게 하느냐(3.2·3.8)가 아니라 어떻게 운영하느냐를 다룬다. 데이터에서 재학습까지 실행되는 닫힌 루프, 모델·어댑터의 레지스트리와 버전, 무엇으로 만들었는지 되짚는 계보와 재현성, 릴리스 승인·감사, 언제 다시 튜닝하는가, 베이스 세대가 바뀔 때의 이관, 그리고 태스크와 고객이 늘며 곱으로 폭증하는 버전 급증과 그 폐기까지. 등장하는 레지스트리 필드·게이트 임계·툴 동작은 조직과 도구마다 다르니, 무게는 특정 제품이 아니라 무엇을 왜 추적하고 통제하는가에 둔다.
8.3.1 파인튜닝 모델은 왜 노후화되는가
"한 번 튜닝하면 끝"이라는 심상은 네 방향에서 동시에 붕괴한다. 이 넷이 나머지 절 전부의 동기다.
첫째, 세상이 변한다. 파인튜닝 모델은 학습 당시의 입력 분포·용어·정책·상품 목록을 각인한다. 그런데 사용자 질문의 주제가 이동하고(입력 드리프트), 무엇이 좋은 답인지의 기준이 바뀌고(개념 드리프트), 새 상품·정책·법규가 등장한다. 학습 시점에 좋던 모델이 가만히 있어도 서서히 나빠진다. 이 흐름을 감지해 환류시키는 것이 데이터 플라이휠(8.1)이고, 그 피드백이 재학습 트리거다.
둘째, 베이스 모델이 교체된다. 대부분의 팀은 남의 오픈웨이트나 API 모델 위에 파인튜닝한다. 그 베이스는 반년에서 1년 단위로 더 똑똑하고 저렴하고 빠른 새 버전이 나오고 옛 버전은 폐기 예고된다. 새 베이스로 갈아타려면 파인튜닝을 그 위에 다시 추가해야 하는데, 어댑터는 특정 베이스 가중치에 묶여 있어 그대로 옮겨지지 않는다.
셋째, 파인튜닝은 지식이 아니다. 앞에서 말한 그대로다. 지식이 자주 바뀌는 도메인에서 파인튜닝으로 최신성을 유지하려 들면, 재학습이 지식 갱신 주기를 따라잡지 못해 비용만 폭증한다. 이 경계를 지키는 것 자체가 라이프사이클 비용을 줄이는 가장 큰 결정이다.
넷째, 버전이 급증한다. 태스크가 늘고(요약·분류·상담), 테넌트가 늘고(고객사별 톤), 베이스가 갱신되고, 실험 리비전이 쌓이면 관리 대상 조합이 곱셈으로 늘어난다. 추적·평가·폐기 체계 없이 방치하면 "어느 모델이 프로덕션이고 무엇으로 만들었는지 아무도 모르는" 상태로 수렴한다.
파인튜닝을 시작하기 전에 팀이 던져야 할 핵심 질문은 "어떻게 튜닝하나"가 아니라 "이 모델을 앞으로 몇 년간 유지보수할 여력이 있나"다. 여력이 없다면 프롬프트와 RAG로 대응하는 편이 나은 경우가 많다(9.1). 파인튜닝의 실제 비용은 첫 학습이 아니라 그 뒤에 이어지는 라이프사이클 전체에 있고, 이 장은 그 전체를 어떻게 다루는지에 관한 것이다.
8.3.2 닫힌 루프와 그것을 지키는 게이트
운영되는 파인튜닝은 선형 절차가 아니라 닫힌 루프다. 데이터 준비 → 학습 → 평가 → 배포 → 모니터링 → 피드백 → 다시 데이터 준비로 실행된다. 각 화살표에는 게이트, 곧 다음 단계로 넘어가려면 통과해야 하는 조건이 걸린다. 게이트가 없는 파이프라인은 나쁜 모델을 자동으로 배포하는 기계일 뿐이다.
각 단계와 그 게이트를 정리하면 이렇다.
| 단계 | 하는 일 | 통과 게이트(예) | 연결 |
|---|---|---|---|
| ① 데이터 준비 | 플라이휠·로그·시연에서 수집, 정제·중복제거·PII 제거·라벨·분할 | 학습/검증/테스트 누출 없음, 스키마·라벨 품질 통과 | 2.1, 8.1, 9.3 |
| ② 학습 | SFT 또는 LoRA/QLoRA 실행, 체크포인트·로그 기록 | 손실 수렴, 발산·스파이크 없음 | 3.2, 3.8, 2.6 |
| ③ 평가 | 오프라인 벤치·태스크셋·LLM-judge·회귀셋·안전셋 | 목표 지표 임계 이상, 회귀셋 무저하, 안전 통과 | 4.1, 4.2, 4.3 |
| ④ 배포 | 레지스트리 등록·모델 카드·승인·카나리·점진 롤아웃 | 승인권자 서명, 카나리 온라인 지표 정상 | 8.2, 7.2 |
| ⑤ 모니터링 | 품질·드리프트·비용·지연·안전 신호 상시 관측 | (관측이 목적 — 트리거를 다음 루프로) | 7.6, 8.1 |
이 루프를 지탱하는 원칙은 셋이다. 하나, 데이터는 코드처럼 다룬다. 학습 데이터셋에 해시와 스냅샷으로 버전을 매기고 테스트셋을 학습에서 격리한다. "지난번보다 좋아졌다"를 말하려면 버전 간에 고정된 평가셋이 있어야 하고, 평가셋이 학습으로 유입되어면(누출) 그 좋아진 숫자는 거짓이다(4.1). 둘, 평가 게이트가 배포를 막는다. 오프라인 게이트를 통과해야 배포 후보가 되고, 배포는 다시 카나리·A/B(8.2)로 온라인에서 검증한다. 오프라인이 좋아도 온라인이 나쁠 수 있는데, 이것은 오프라인 평가셋의 분포와 실제 트래픽의 분포가 다르기 때문이다. 셋, 모니터링이 루프를 닫는다. 배포가 끝이 아니라, 모니터링(7.6)이 다음 재학습의 트리거와 데이터를 만든다. 이 피드백이 없으면 그것은 닫힌 루프가 아니라 한 번 쓰고 버리는 절차다.
이 루프를 어디까지 자동화할지는 위험도와 성숙도의 함수다. 초기·고위험 도메인에서는 모든 단계에 사람이 붙어 학습을 트리거하고 배포를 승인한다. 성숙해지면 데이터·학습·평가는 자동으로 실행하고 배포 승인만 사람이 쥐는 반자동으로 넘어가는데, 대부분의 팀이 오래 머무는 지점이다. 저위험에서 더 확대하면 트리거되면 학습·평가·카나리까지 자동으로 흐르고 게이트 실패 시 자동 중단하는 연속 학습 파이프라인이 된다. 다만 "MLOps 자동화"를 무비판적으로 최대치로 밀면 나쁜 데이터가 자동으로 학습되고 자동으로 배포되는 파이프라인을 만들게 된다. 자동화는 게이트를 더 엄격히 만든 다음에야 안전하다 — 자동화는 게이트를 생략할 핑계가 아니라 게이트를 더 강화해야 할 이유다. 특히 완전 자동 재학습은 파국적 망각·피드백 오염·모델 붕괴(3.9)의 위험을 안고 있어, 자동화하더라도 회귀 게이트와 롤백은 반드시 건다.
8.3.3 레지스트리에 등록되는 아티팩트는 정확히 무엇인가
파이프라인의 산출물은 어딘가에 등록·버전·검색되어야 한다. 그 중심 저장소가 모델 레지스트리(model registry)다. 레지스트리는 파일 창고가 아니라 "어떤 아티팩트가, 무엇으로 만들어졌고, 지금 어느 단계에 있는가"를 아는 대장(臺帳)이다. 엔트리 하나에는 세 부분이 붙는다 — 실제 산출물인 아티팩트, 그것을 설명하는 메타데이터, 그리고 그것이 무엇으로부터 나왔는지를 잇는 계보다.
아티팩트가 실제로 얼마나 부하가 큰지부터 세어 보자. 전체 파인튜닝이면 가중치 체크포인트 전부라 베이스 크기와 맞먹지만, LoRA 어댑터는 훨씬 경량이다. 랭크 $r$ LoRA는 대상 가중치 $W\in\mathbb{R}^{d_{out}\times d_{in}}$ 에 $B\in\mathbb{R}^{d_{out}\times r}$ 와 $A\in\mathbb{R}^{r\times d_{in}}$ 를 더하므로(3.8), 행렬 하나가 더하는 파라미터는 $r\,(d_{in}+d_{out})$ 이다. $d_{model}=4096$ 인 7B급 모델에 $r=16$ 으로 어텐션의 네 투영($W_Q,W_K,W_V,W_O$, 각 $4096\times4096$)을 32개 층 전부에 붙이면,
$$N_{\text{adapter}} = n_{\text{layers}}\times 4 \times r\,(d_{in}+d_{out}) = 32 \times 4 \times 16 \times (4096+4096) = 16{,}777{,}216 \approx 16.8\text{M}$$
파라미터가 나온다. fp16(원소당 2바이트)으로 저장하면 $16.8\text{M}\times2 = 33.6$ MB다. 베이스가 14 GB인데 어댑터는 34 MB — 베이스의 0.24%다. MLP 투영까지 대상에 넣으면 이 값이 두세 배로 커지지만, 여전히 100 MB 안쪽이다. 이 경량성이 어댑터를 수백 개씩 만들 수 있게 하는 물리적 이유이자, 곧 보게 될 버전 급증의 물리적 이유이기도 하다. 레지스트리에는 이 어댑터 파일과 함께 어느 베이스에 붙는지, 어떤 토크나이저·챗 템플릿·양자화 설정으로 쓰는지가 반드시 따라붙어야 한다 — 이것들이 빠지면 서빙에서 어댑터를 잘못된 베이스나 템플릿에 추가해 조용히 망가진다.
메타데이터는 버전·스테이지·태스크·소유자·생성 시각·평가 점수·모델 카드 링크를 담고, 계보는 이 아티팩트를 낳은 데이터셋 버전·학습 설정·시드·베이스 모델·코드 커밋을 담는다. 계보는 다음 절에서 따로 다룬다.
핵심은 베이스와 어댑터를 분리해 버전을 매긴다는 점이다. 베이스는 읽기 전용으로 공유되고, 어댑터는 "어느 베이스에 붙는지"를 명시적으로 묶어 버전한다. 그림의 [email protected]과 [email protected]은 이름은 이어지지만 서로 다른 베이스에 붙는 다른 아티팩트다. 이 묶임을 메타데이터에 기록하지 않으면, 베이스를 업그레이드하는 순간 어댑터가 어느 세대에 속하는지 추적이 끊긴다.
8.3.4 버전을 의미 있게 매기기 — 스테이지와 별칭의 분리
버전 번호를 의미 있게 매기면 "이게 큰 변경인지 미세 수정인지"가 번호만 봐도 보인다. 소프트웨어의 유의적 버저닝을 모델에 빌려 온 한 가지 규약은 이렇다.
| 자리 | 높이는 경우 | 예 |
|---|---|---|
| MAJOR | 베이스 모델 교체, 또는 입출력 스키마·챗 템플릿이 바뀌어 호환이 깨지는 변경 | 1.3 → 2.0 |
| MINOR | 같은 베이스에서 데이터·목표를 크게 바꿔 행동이 변하는 재학습 | 1.3 → 1.4 |
| PATCH | 소규모 데이터 보강·버그픽스·재현 재학습(행동 거의 불변) | 1.3 → 1.3.1 |
사람이 읽는 이 버전은 소통용이다. 진실의 원천은 따로 있어야 하는데, 아티팩트의 콘텐츠 해시(내용이 1비트라도 다르면 다른 해시)와 그것을 만든 학습 실행 ID다. "프로덕션에 지금 정확히 무엇이 떠 있나"라는 질문은 태그가 아니라 해시로 답해야 한다. 태그는 사람이 옮길 수 있고 실수로 잘못 붙을 수 있지만, 해시는 내용 그 자체를 가리키기 때문이다.
여기서 스테이지와 별칭을 반드시 분리해야 한다. 레지스트리는 보통 staging·production·archived 같은 스테이지를 두고, production 같은 움직이는 별칭이 특정 버전을 가리킨다. 배포와 롤백은 새 아티팩트를 만드는 일이 아니라 별칭을 이미 존재하는 다른 버전으로 옮기는 일이다. 이렇게 설계하면 롤백이 재빌드 없이 즉각적이고, 롤백해도 원래 버전의 아티팩트와 계보가 그대로 남아 사후 분석이 가능하다. 서빙 라우터는 이 별칭을 따라가므로(8.3.11), 별칭이 곧 "무엇이 트래픽을 받는가"의 단일 진실이 된다.
# 레지스트리 엔트리(개념 예시 — 실제 스키마는 툴마다 다름)
name: adapter/summary
version: "2.0"
content_hash: "sha256:7f8e9a…" # 진실의 원천(불변)
run_id: "run-2025-07-14-0093"
stage: production # staging | production | archived
type: lora # lora | qlora | full-ft
base_model: { name: base, version: v2, hash: "c3d4…" }
artifact_uri: "s3://models/summary/2.0/adapter.safetensors"
tokenizer: "chat-template-v3"
task: summarization
owner: team-nlp
created_at: "2025-07-14T09:12:00Z"
lineage: { dataset: "ds-sum@2025-07#a9…", config: "cfg#b2…", seed: 42, code: "git:9f8e…" }
eval: { judge_win_rate: 0.62, regression_set: "pass", safety: "pass" }
model_card: "/cards/summary-2.0"
approvals: [ { by: "@lead", at: "2025-07-15T…", note: "카나리 정상" } ]
이 필드 구성과 스테이지 전환 동작은 개념 예시다. MLflow·W&B·SageMaker·Vertex나 자체 구현마다 용어와 기능이 다르니 특정 제품의 정확한 동작은 공식 문서로 확인해야 한다. 바뀌지 않는 것은 무엇을 반드시 담아야 하는가다 — 아티팩트, 메타데이터, 계보, 그리고 배포·롤백을 감당하는 스테이지·별칭 구조.
8.3.5 계보 — 무엇으로 만들었나
레지스트리 엔트리가 "지금 무엇이 있나"라면, 계보(lineage)는 "그것이 무엇으로부터 나왔나"를 잇는 방향 그래프다. 계보가 완전하면 임의의 프로덕션 모델에서 출발해 데이터·설정·시드·베이스·코드까지 역추적할 수 있고, 이 역추적성이 감사·디버깅·재현·규제 대응의 토대가 된다.
이 그래프에서 결정적인 것은 각 노드가 불변 식별자로 고정되어야 한다는 점이다. 데이터셋은 "대충 최신 데이터"가 아니라 해시 붙은 스냅샷이어야 하고, 베이스는 버전 태그가 아니라 가중치 해시여야 하고, 코드는 브랜치 이름이 아니라 커밋 SHA여야 하며, 여기에 라이브러리 버전까지 핀으로 고정돼야 한다. 노드 하나라도 움직이는 참조로 남겨 두면 계보가 그 지점에서 끊긴다 — "[email protected]은 base@v2 위에서 ds-sum@2025-07을 시드 42로 학습한 결과"라는 문장이 검증 가능한 사실이 되려면, 그 문장에 등장하는 모든 명사가 고정된 대상을 가리켜야 한다. 그리고 이 계보는 학습 시점에만 저렴하게 남길 수 있다. 학습이 끝나고 몇 주 뒤에 "그 모델 무슨 데이터로 만들었지"를 복원하려 들면 로그 고고학이 되고, 대개 실패한다.
8.3.6 왜 파인튜닝은 재현이 어려운가
"같은 데이터, 같은 설정, 같은 시드면 같은 모델이 나오지 않나?" 대체로 아니다. 이것을 이해하려면 부동소수점 산술의 한 가지 사실에서 시작해야 한다 — 덧셈에 결합법칙이 성립하지 않는다. 무한 정밀도라면 $(a+b)+c = a+(b+c)$ 지만, 유한 비트에서는 각 덧셈마다 반올림이 일어나 순서가 결과를 바꾼다.
fp16으로 구체적으로 해 보자. fp16은 가수가 10비트라, 값 $1.0$ 부근의 간격(ulp)이 $2^{-10}\approx 0.000977$ 이고 그 절반인 $0.000488$ 보다 작은 변화는 반올림으로 사라진다. $a=1.0,\ b=0.0003,\ c=0.0003$ 을 두 순서로 더해 보면,
$$(a+b)+c:\quad 1.0+0.0003 \to 1.0\ (\text{0.0003 < 반-ulp라 흡수}),\quad 1.0+0.0003 \to 1.0$$
$$a+(b+c):\quad 0.0003+0.0003 = 0.0006,\quad 1.0+0.0006 \to 1.000977\ (\text{반-ulp 넘어 올림})$$
같은 세 수를 더했는데 순서에 따라 $1.0$ 과 $1.000977$ 로 갈린다. GPU는 수천 개 스레드가 병렬로 부분합을 만들고 그것을 축약(reduction)하는데, 그 축약 순서가 실행마다 달라질 수 있다. 그러면 방금 본 것처럼 마지막 비트가 달라지고, 그 미세한 차이가 긴 학습 궤적에서 증폭되어 다른 최소값으로 수렴할 수 있다. 혼합정밀도(BF16/FP16, 2.6)에서는 가수가 짧아 이 오차가 더 크게 벌어진다.
부동소수점만이 원인은 아니다. 같은 코드도 GPU 세대·CUDA/커널 버전·cudnn.benchmark 같은 자동 튜닝에 따라 다른 연산 경로를 타 결과가 달라지므로, 다른 하드웨어에서 비트 단위 재현은 사실상 불가능하다고 보는 편이 안전하다. 분산학습(2.4)에서 그래디언트 all-reduce 순서·워커 수·배치 구성이 바뀌어도, 데이터로더의 셔플 시드·동적 패딩·멀티프로세스 순서가 바뀌어도, 몇 달 뒤 transformers·peft·torch·드라이버 버전이 올라 기본값이 바뀌어도 결과가 달라진다. 재현성은 우리가 고정할 수 있는 것과 고정하기 어려운 것의 함수다.
$$\text{재현성} \;=\; f\big(\underbrace{\text{데이터·설정·시드}}_{\text{우리가 고정}},\; \underbrace{\text{FP·커널·하드웨어·라이브러리}}_{\text{고정하기 어려움}}\big)$$
그래서 두 종류의 재현을 구분해야 한다. 비트 재현(bitwise reproducibility)은 결정론적 커널·고정 하드웨어·완전히 핀된 환경(컨테이너 이미지 해시까지)을 요구해 비용이 크고 느리며, 대개 불필요하다. 운영에서 실제로 원하는 것은 통계적 재현이다 — 같은 레시피로 다시 실행하면 평가 지표가 유의미하게 같은 모델이 나온다는 것. 재현 수준은 강요가 아니라 선택이다.
| 수준 | 무엇을 고정 | 얻는 것 | 비용 |
|---|---|---|---|
| L0 계보만 | 데이터·설정·베이스·코드를 기록(고정은 안 함) | 감사·역추적 | 낮음 |
| L1 레시피 재현 | 위 + 컨테이너 이미지·라이브러리 버전 핀·시드 | 통계적 재현(지표 재현) | 중간 |
| L2 비트 재현 | 위 + 결정론 커널·단일 하드웨어·연산 순서 고정 | 비트 단위 동일 | 높음(느림·제약) |
대부분의 팀에게 실용적 목표는 L1이다. 계보를 완전히 기록하고, 환경을 컨테이너로 핀하고, 시드를 고정해 "지표가 재현되면 성공"으로 정의한다. L2 비트 재현은 규제·안전 인증처럼 법적으로 "정확히 그 모델"임을 증명해야 할 때만 값을 한다. "재현은 곧 비트 동일"이라는 완벽주의는 파인튜닝에서 달성 불가에 가깝고 비생산적이다. 노력을 집중할 곳은 오히려 계보로 역추적성을 100% 확보하는 쪽이다 — 감사와 디버깅의 대부분은 결과를 비트까지 재현하지 못해도 "무엇으로 만들었나"만 알면 풀린다.
8.3.7 거버넌스 — 카드, 서명, 그리고 지워지지 않는 로그
모델이 사용자에게 영향을 주는 순간, "누가 무엇을 왜 배포했나"는 기술 문제이자 책임 문제가 된다. 거버넌스는 이 책임을 문서·승인·기록으로 제도화한다. 규모가 작을 때는 과해 보이지만, 규제 도메인(금융·의료·공공)이나 다수 팀·감사 대상 조직에서는 이것이 없으면 배포 자체가 막힌다.
모델 카드(model card)는 모델의 성분표이자 주의사항이다. 파인튜닝 모델 카드의 최소 항목은 다음과 같다.
| 구획 | 담는 것 |
|---|---|
| 식별 | 이름·버전·해시·베이스 모델·계보 링크 |
| 의도된 용도 | 이 모델이 하도록 만든 태스크·대상 사용자 |
| 금지·범위 밖 용도 | 하면 안 되는 것(의료 진단·법률 자문 등), 알려진 실패 모드 |
| 학습 데이터 | 출처·기간·규모·전처리·알려진 편향과 공백(2.1·9.4) |
| 평가 | 벤치·회귀셋·안전셋 결과, 그리고 평가 자체의 한계(오염 가능성 4.1) |
| 안전·윤리 | 유해성·편향 평가(4.3·9.4), 완화책, 남은 위험 |
| 운영 | 서빙 요구(베이스·양자화·라우팅), 폐기 계획, 연락처 |
모델 카드는 레지스트리 엔트리에 링크되어 아티팩트와 함께 버전된다. "카드 없는 모델은 프로덕션 승인 불가"를 게이트로 적용하면, 카드가 사후에 쓰는 문서가 아니라 배포 조건이 된다 — 카드를 안 쓰면 배포가 안 되므로, 학습을 끝낸 사람이 기억이 생생할 때 채운다.
배포 게이트의 마지막은 사람의 승인이다. 최소 설계는 태스크·위험도별로 누가 서명할 권한이 있는지 정하고(저위험은 팀리드, 고위험·규제는 안전·법무 포함), 승인 조건을 체크리스트로 고정하고(평가 게이트 통과 + 회귀셋 무저하 + 안전셋 통과 + 카드 완비 + 롤백 계획 존재), 누가·언제·어느 해시를·왜 승인했는지를 변경 불가 로그로 남기는 것이다.
감사(audit)가 요구하는 것은 단순하다. 임의 시점에 프로덕션에 무엇이 배포되어 있었고, 그것은 누가 언제 왜 배포했으며, 무엇으로 만들었나에 답할 수 있어야 한다. 이를 위해 남기는 불변 이벤트는 스테이지 전환(별칭이 언제 어느 해시로 이동했나, 배포와 롤백 모두), 승인·거부·예외 이벤트와 그 사유, 데이터셋·모델 카드 변경과 폐기 결정이다. 이 감사 로그가 계보와 함께 있으면, 사고가 났을 때 "언제부터 어느 모델이, 무슨 데이터로 만든 것이 문제였나"를 몇 분 안에 답할 수 있다. 없으면 며칠이 걸리거나 영원히 모른다. 거버넌스의 구체 요구 수준은 도메인과 법에 따라 크게 다르지만(9.3), 모델 카드·승인 서명·불변 배포 로그 이 셋만큼은 규모와 무관하게 저렴하고 효과가 크며, 무엇보다 나중에 소급해서 만들기가 가장 어려운 것들이다.
8.3.8 언제 다시 튜닝하나
"얼마나 자주 재학습하나"에 고정된 답은 없다. 일정 기반과 트리거 기반을 두 축으로 설계한다. 먼저 무엇이 재학습을 부르는지 트리거를 넷으로 나눠 보자.
드리프트 트리거는 입력 분포가 이동하거나(새 주제·용어·언어 비중 상승) 정답 기준이 바뀔 때(개념 드리프트) 걸린다. 임베딩 분포 거리·주제 분류 이동·신규 토큰 비율로 감지한다(7.6). 품질 트리거는 온라인 지표(사용자 만족·재시도율·이의제기·사람 검수 통과율)가 임계 아래로 떨어질 때 걸리는데, 가장 직접적인 신호이지만 지연이 있고 측정 자체가 어렵다(4.2). 데이터 트리거는 플라이휠(8.1)로 새 학습 데이터가 충분히 쌓였을 때 걸린다. 베이스·정책 트리거는 베이스 모델이 업그레이드되거나 안전·규제 요건이 바뀔 때 재학습·재평가를 강제한다.
데이터 트리거는 계산으로 주기를 정할 수 있다. 플라이휠이 주당 교정된 엣지케이스 500건을 만들고, 경험상 새 라벨 5,000건이 쌓여야 재학습이 지표를 유의미하게 움직인다면, 재학습 주기는 자연히
$$T_{\text{재학습}} = \frac{5{,}000\ \text{건}}{500\ \text{건/주}} = 10\ \text{주}$$
가 된다. 이 계산의 쓸모는 숫자 자체가 아니라 두 가지를 드러낸다는 데 있다. 하나, 드리프트가 가속하면 주당 생성량 자체가 오르므로(예: 500 → 800건/주) 주기가 저절로 짧아진다 — 즉 데이터 트리거는 세상의 변화 속도에 자동으로 반응한다. 둘, 임계를 너무 낮게 설정하면(예: 500건마다) 매주 재학습하며 파국적 망각을 누적하고, 너무 높게 설정하면(예: 50,000건마다) 노후화된 모델을 오래 끌고 간다. 임계는 "재학습이 회귀셋을 흔들지 않으면서 목표 지표를 움직이는 최소량"으로 실측해 정해야 한다.
여기서 가장 흔한 오답을 살펴봐야 한다. 트리거가 왔다고 곧장 재학습이 답은 아니다. 지표가 떨어진 원인이 최신 지식의 부재면 RAG 갱신(6.3)이 재학습보다 저렴하고 빠른 정답이고, 소소한 행동 교정이면 프롬프트·정책 수정(6.1·8.2)으로 충분하며, 오직 행동·형식·문체의 각인 자체를 바꿔야 할 때만 재학습이다. "성능이 떨어졌으니 재학습"은 종종 가장 비용이 큰 오답인데, 원인을 진단하지 않고 반사적으로 가장 부담이 큰 수단을 택하기 때문이다.
일정과 트리거는 섞어 쓴다. 드리프트가 완만하고 예측 가능하면 분기마다 같은 정기 재평가가 예측 가능하고 계획이 쉽지만 급변에는 느리다. 트리거 기반은 필요할 때만 반응이 빠르지만 오탐과 피드백 오염 위험이 있다. 실무의 안전한 기본값은 혼합이다 — 정기 재평가로 서서히 노후화를 검출하고, 트리거로 급격한 드리프트·품질 급락·플라이휠 임계에 즉응한다. 단, 완전 자동 재학습 루프는 피드백 오염·모델 붕괴(3.9)·파국적 망각(8.3.12)을 조심해야 하므로, 자동화하더라도 회귀 게이트와 사람 승인을 반드시 건다.
8.3.9 베이스가 교체될 때 — 파인튜닝 이관
라이프사이클에서 가장 크고 자주 과소평가되는 사건이 베이스 모델 업그레이드다. 새 베이스는 더 똑똑하고 저렴하고 컨텍스트가 길지만, 문제는 이것이다 — 어댑터와 파인튜닝은 특정 베이스의 가중치 공간에 묶여 있어 그대로 옮겨지지 않는다.
왜 못 옮기는지는 LoRA의 수식에 그대로 있다. 어댑터는 $\Delta W = \frac{\alpha}{r}BA$ 로, 특정 베이스 가중치 $W_0$ 에 더해질 것을 전제로 학습된 잔차다(3.8). 새 베이스 $W_0'$ 는 값이 완전히 다르고(다른 데이터로 다르게 학습됨), 심지어 차원·층 수·어텐션 구조(GQA 등, 1.6)·토크나이저·챗 템플릿까지 다를 수 있다. $W_0$ 에 맞춰 학습한 $BA$ 를 $W_0'$ 에 더하는 것은 의미가 없다 — 좋게 봐도 무작위 교란이고 나쁘면 파손이다. 전체 파인튜닝 모델은 더 노골적이다. 그것은 단순히 옛 베이스 그 자체라 새 베이스와는 다른 모델이다. 결론은, 베이스가 바뀌면 파인튜닝은 원칙적으로 새 베이스 위에서 다시 해야 한다. 다행히 우리는 레시피(데이터·설정·계보, 8.3.5)를 갖고 있으므로 처음부터가 아니라 "레시피를 새 베이스에 재적용"이다.
각 단계의 요점은 이렇다. 호환성 점검(1)에서 토크나이저·챗 템플릿이 다르면 데이터 포맷팅부터 새로 해야 하고(3.2), 컨텍스트 길이·라이선스(상업 이용 조건, 9.2)·프라이버시 조건(9.3)도 함께 확인해야 한다. "새 베이스가 더 좋다"만 보고 갈아탔다가 라이선스나 템플릿에서 막히는 일이 흔하다. 가장 중요하면서 가장 자주 생략되는 단계는 베이스만으로 재측정(2)이다. 새 베이스는 옛 베이스보다 강해서, 옛날엔 파인튜닝이 필요했던 태스크를 새 베이스는 프롬프트만으로 해낼 수 있다. 그러면 이관이 아니라 폐기가 정답이다 — 유지보수 부담 전체를 없애는 최고의 결과다. 이 확인 없이 관성으로 재학습하면 불필요한 어댑터를 계속 끌고 간다. 레시피 재적용(3)에서 계보의 값이 증명되고, 하이퍼파라미터 재튜닝(4)에서는 옛 베이스의 최적 lr·r·대상층이 새 베이스에서도 최적이라는 보장이 없어 소규모 스윕이 필요할 수 있다. 신·구 비교(5)는 반드시 고정된 같은 평가셋으로 "옛 베이스+어댑터" 대 "새 베이스+어댑터"를 세우고 회귀셋을 포함한다 — "새 베이스니 당연히 낫겠지"는 검증 없는 가정이고, 특정 태스크·톤에서 퇴보할 수 있다. 병행 배포(6)는 즉시 전환 대신 카나리로 온라인 검증하고, 구버전 폐기(7)는 새 버전이 안정화될 때까지 롤백 경로를 유지하며 유예를 둔다.
이관 비용이 왜 무서운지는 어댑터 수를 곱해 보면 드러난다. 앞서 센 34 MB 크기의 LoRA 어댑터를 300개 운용하는 팀을 가정하자(다음 절의 버전 급증이 정확히 이 규모를 만든다). 어댑터 하나를 새 베이스에 재학습하는 데 대략 2 GPU-시간, 하이퍼파라미터 미니 스윕과 동일 평가셋 비교에 1 GPU-시간을 더해 왕복 3 GPU-시간이라 치면,
$$300\ \text{어댑터} \times 3\ \text{GPU-시간} = 900\ \text{GPU-시간}$$
이고, GPU-시간당 대략 \$2로 가정하면 컴퓨트만 \$1,800이다(수치는 개략 규모 파악용). 그런데 이 컴퓨트 비용은 사실 저렴한 쪽이다. 실제 부담은 300개 각각에 대한 평가 검토·회귀 확인·모델 카드 갱신·승인 서명이라는 사람의 일이고, 이것은 자동화하기 어렵다. 베이스 업그레이드가 연 1~2회 온다면 이 비용이 그만큼 반복된다. 그래서 이관을 앞두고 가장 값진 작업은 재학습이 아니라 폐기다 — 300개 중 정말 살아 있는 어댑터가 몇 개인지 가려내, 운용하는 수 자체를 줄이는 것이다. 9.1의 build-vs-buy에서 "파인튜닝은 지속 비용을 만든다"는 논지의 구체적 실체가 바로 이 곱셈이다. 결정할 때는 첫 학습 비용이 아니라 이관 비용까지 계산해야 한다.
8.3.10 버전 급증의 계산
태스크·테넌트가 하나면 관리는 쉽다. 문제는 축이 늘 때 곱셈으로 폭증한다는 것이다. 관리해야 할 아티팩트 수는 대략
$$N_{\text{artifact}} \;\approx\; N_{\text{task}} \times N_{\text{tenant}} \times N_{\text{base-ver}} \times N_{\text{revision}}$$
가 된다. 태스크 5개, 테넌트 10곳(테넌트별 톤), 병행하는 베이스 버전 2개, 리비전 3개면 이미 $5\times10\times2\times3 = 300$ 개 어댑터다. 앞 절에서 어댑터 하나가 34 MB였으니 저장만 $300\times34\ \text{MB}\approx 10\ \text{GB}$ 인데, 스테이징·실험·롤백용 사본까지 더하면 저장은 문제 축에도 포함되지 못한다. 실제 문제는 이 300개 각각이 학습·평가·문서·이관·폐기의 대상이라는 점이다. 관리 부재는 곧 "살아 있는지 죽었는지 모르는 좀비 모델"의 양산이다.
폭발을 다스리는 방법은 세 갈래다. 첫째, 공통 규격을 강제한다. 어댑터의 랭크·$\alpha$·대상층·토크나이저를 팀 표준으로 통일하면, 이질적일 때 복잡해지는 멀티-LoRA 서빙 커널(5.6)과 제각각이 되는 평가·이관이 단순해진다. 둘째, 차원 자체를 줄인다. "테넌트마다 어댑터"는 급증의 주범인데, 정말 테넌트별 파인튜닝이 필요한지 되물어야 한다. 많은 경우 테넌트 차이는 프롬프트·시스템 메시지·RAG로 흡수할 수 있고(6.1·6.3·7.2), 파인튜닝은 개별 테넌트가 아니라 세그먼트(업종·언어) 단위로 묶는 편이 관리 가능하다 — 위 곱셈에서 $N_{\text{tenant}}=10$ 을 세그먼트 $2$ 로 줄이면 300개가 60개로 내려간다. 셋째, 적극적으로 폐기한다. 안 쓰는 어댑터는 아카이브·삭제하고, 모든 엔트리에 소유자·용도·마지막 사용 시각·만료(TTL)를 붙여 무주공산 모델을 정기적으로 청소한다.
8.3.11 멀티-LoRA 운영과 안전한 폐기
다수 어댑터의 런타임 쪽은 3.8·5.6에서 다룬 멀티-LoRA 서빙이다. 베이스는 GPU에 한 벌만 적재하고, 요청마다 해당 어댑터의 $\frac{\alpha}{r}BA$ 만 추가한다. 라이프사이클 관점에서 여기에 덧붙는 요구는 두 가지다. 하나는 라우팅과 버전의 결합이다. 요청이 들어오면 (태스크·테넌트)에서 어느 어댑터의 어느 버전을 적용할지 라우터가 결정하는데, 이 라우팅의 진실은 8.3.4의 production 별칭이어야 한다 — 배포와 롤백은 별칭 이동으로 일어나고, 라우터는 별칭을 따르므로, 별칭 하나만 옮기면 재배포 없이 트래픽이 새 버전으로 넘어간다. 다른 하나는 웜/콜드 관리다. 활성 어댑터가 GPU 상주 한도를 넘으면 자주 쓰는 것만 GPU에 두고 나머지는 CPU/디스크에서 스왑하는데(3.8), 어느 어댑터가 뜨거운지를 모니터링(7.6)해 상주 정책과 폐기 결정을 잇는다 — 몇 주째 콜드인 어댑터는 폐기 후보다.
모델을 없애는 것도 절차다. 갑자기 지우면 그것에 의존하던 트래픽이 깨진다. 폐기는 상태 기계로 다룬다.
폐기 전에는 대체 버전과 마이그레이션 경로를 알린다(내부 태스크든 외부 API 고객이든, 7.2). 그리고 아티팩트를 지워도 계보·모델 카드·감사 로그는 보존한다 — "그때 그 결정을 무엇으로 내렸나"에 답할 수 있어야 하기 때문이다. 최종 삭제는 법적 보존 기한과 프라이버시 삭제 요건(9.3)을 함께 확인해 진행한다. 이 둘이 충돌할 수 있는데(감사를 위해 남겨야 하는 것과 프라이버시를 위해 지워야 하는 것), 그 조정은 법·정책의 문제이므로 이 절차의 마지막 단계에 명시적 확인을 둔다.
8.3.12 계속 학습하면 계속 나빠질 수 있다
라이프사이클은 "계속 재학습"을 뜻하는데, 계속 학습하면 계속 나빠질 위험이 있다. 두 측면으로 나타난다.
하나는 파국적 망각이다. 새 데이터로 미세조정할 때 모델이 이전에 잘하던 것을 잊는 현상으로(3.2), 라이프사이클에서는 이것이 세대에 걸쳐 누적된다. 재학습하면 새 태스크는 좋아지지만 옛 능력·안전 정렬(3.1)·다른 태스크가 조금씩 후퇴하고, 또 재학습하면 또 후퇴하며, 여러 세대를 지나는 동안 서서히 붕괴한다. 완화책은 새 데이터에 옛 능력·일반 지시·안전 데이터를 섞어 옛 분포를 상기시키는 리허설, 베이스를 동결해 구조적으로 지식 파괴가 덜한 PEFT(3.8 — 어댑터를 떼면 원상복구도 된다), 그리고 과도하게 진행하지 않는 낮은 학습률·조기 종료다. 다만 PEFT라도 어댑터 자체는 이전 어댑터 대비 후퇴할 수 있으므로, 동결이 회귀 게이트를 대신하지는 않는다.
다른 하나는 회귀다. 새 버전이 목표 지표는 높였는데 다른 곳을 몰래 누락하는 현상이다. 목표 지표만 보면 배포하는데, 실제로는 다른 태스크·엣지케이스·안전이 붕괴해 있다. 이것을 검출하는 유일한 방법은 고정된 회귀셋(regression set)으로 배포 전에 막는 것이다(8.2).
회귀셋을 제대로 설계하려면 세 가지를 지켜야 한다. 회귀셋은 학습에 절대 들어가지 않고(누출은 곧 오염, 4.1) 버전 간에 동일하게 유지되어야 비교가 성립한다 — 새 실패를 발견하면 회귀셋에 추가하되(플라이휠, 8.1) 기존 항목은 그대로 둔다. 담는 내용은 자주 쓰는 핵심 능력과 과거에 발생했던 케이스, 그리고 안전·정렬 프로브(4.3)를 함께 넣는다. 마지막으로 회귀셋 통과를 배포 파이프라인의 강제 게이트로 적용해, 사람이 "이번엔 괜찮겠지"로 건너뛸 수 없게 한다.
그런데 오프라인 회귀셋을 통과해도 온라인에서 회귀할 수 있다. 오프라인 평가셋의 분포와 실제 트래픽의 분포가 다르기 때문이다. 그래서 오프라인 게이트 다음에 카나리·점진 롤아웃·자동 롤백(8.2)이 필요하다. 평가 통과는 "안전"이 아니라 "배포를 시도해도 될 후보"라는 뜻이고, 오프라인 게이트와 온라인 카나리라는 두 층이 함께여야 지속 업데이트가 세대에 걸쳐 서서히 붕괴하는 것을 막는다.
파인튜닝된 모델은 만드는 순간이 아니라 계속 유지하는 자산이다. 레지스트리로 무엇이 있는지 알고, 계보로 무엇으로 만들었는지 되짚고, 게이트로 나빠짐을 막고, 거버넌스로 책임을 남기고, 이관으로 베이스 세대를 건넌다. "얼마나 좋은 모델을 만드느냐"만큼이나 "그 모델을 어떻게 오래 안전하게 굴리느냐"가 실제 서비스의 성패를 가른다 — 그리고 지금까지의 이야기는 전부 클라우드 위에서 실행되는 것을 가정했다. 다음 장(8.4)은 이 라이프사이클을 폐쇄망·프라이빗·온디바이스로 가져갈 때 레지스트리·재현·이관이 어떤 제약(하드웨어·격리·업데이트·규제) 아래 어떻게 바뀌는지를 다룬다.