지난 글에서 어댑터를 합칠 것인가 얹어 둘 것인가를 다뤘다. 어느 쪽을 골랐든 그다음에 오는 일은 같다. 학습 스크립트가 끝나고 디렉터리 하나가 생겼는데, 그 디렉터리와 실제 트래픽 사이에는 아직 아무것도 없다.
이 구간이 자주 얇게 다뤄진다. 학습은 노트북과 실험 기록이 있고 서빙은 엔진 문서가 있는데, 그 사이는 각자 알아서 하는 자리라 팀마다 다르게 생겼다. 그러다 어댑터가 셋을 넘어가면 「지금 서버에 올라가 있는 게 어느 버전이지」라는 질문에 아무도 자신 있게 답하지 못하는 상태가 온다.
학습이 끝나면 실제로 무엇이 남는가
PEFT로 학습을 마치면 디렉터리 하나에 파일 두어 개가 남는다. 가중치와 설정이다.
{
"base_model_name_or_path": "meta-llama/Llama-3.1-8B-Instruct",
"peft_type": "LORA",
"r": 16,
"lora_alpha": 32,
"target_modules": ["q_proj", "k_proj", "v_proj", "o_proj"],
"task_type": "CAUSAL_LM"
}
여기서 눈여겨볼 것은 첫 줄이다. 어댑터는 자기 혼자 모델이 아니라 특정 베이스에 붙는 차분이고, 그 베이스가 무엇인지가 설정 파일에 문자열로만 적혀 있다. 문자열이 같아도 그 이름이 가리키는 가중치가 바뀌었으면 — 허깅페이스 리포지터리가 조용히 갱신되었거나, 사내 미러가 다른 리비전을 받아 왔거나 — 어댑터는 아무 오류 없이 올라가서 이상한 답을 낸다.
그래서 등록할 때 이름 대신 커밋 해시를 적는다. meta-llama/Llama-3.1-8B-Instruct@0e9e39f처럼 리비전까지 박아 두면, 나중에 「이 어댑터가 어느 가중치 위에서 학습됐나」에 답할 수 있다. 이름만 적어 둔 어댑터는 반년 뒤에 재현이 안 된다.
이름과 버전이 없으면 배포가 아니다
s3://models/lora_final_v2_real_final/은 배포 가능한 물건이 아니다. 어댑터에도 컨테이너 이미지와 같은 것이 필요하다 — 사람이 부를 수 있는 이름, 순서가 있는 버전, 그리고 그 버전이 무엇으로 만들어졌는지의 기록.
어댑터 레지스트리는 그 셋을 담는 자리다. 모델 레지스트리를 이미 쓰고 있다면 거기에 얹으면 되고, 없으면 객체 저장소 위에 매니페스트 파일 하나로 시작해도 된다. 담을 것은 정해져 있다.
| 필드 | 예시 | 없으면 생기는 일 |
|---|---|---|
| 이름 | support-ko |
용도를 파일 경로로 유추하게 된다 |
| 버전 | v3 |
무엇이 최신인지 사람이 기억해야 한다 |
| 베이스 리비전 | Llama-3.1-8B@0e9e39f |
다른 베이스에 올려도 안 막힌다 |
| 학습 데이터 커밋 | data-repo@a41c9b2 |
재현이 안 되고 원인 추적이 끊긴다 |
| 평가 결과 | 골든셋 82.4, 회귀 0건 | 검증 안 된 것이 배포된다 |
| 학습 설정 | 랭크 16, 3에폭, lr 1e-4 | 다음 학습이 처음부터 다시 더듬는다 |
마지막 두 줄이 중요하다. 평가 결과를 레지스트리에 함께 넣어 두면 배포 게이트를 자동으로 세울 수 있다 — 회귀 테스트를 통과하지 않은 버전은 배포 API가 거절하게 만드는 식이다. 사람의 기억이 아니라 데이터가 문을 지키게 된다.
언제 올리고 언제 내릴 것인가
어댑터를 서버에 올리는 방식은 크게 셋이다. 어느 쪽이 맞는지는 어댑터 개수와 트래픽 모양이 정한다.
| 방식 | 어떻게 | 맞는 자리 | 대가 |
|---|---|---|---|
| 미리 전부 적재 | 기동할 때 전부 GPU에 올린다 | 어댑터 10개 이하, 트래픽이 고르다 | 개수 × 크기만큼 KV 캐시가 줄어든다 |
| 지연 적재 | 첫 요청이 올 때 올린다 | 어댑터 수십 개, 대부분이 한산하다 | 첫 요청에 수백 ms가 얹힌다 |
| LRU 캐시 | 상한을 두고 안 쓰는 것부터 내린다 | 어댑터 수백 개, 꼬리가 길다 | 내려간 어댑터가 다시 불리면 다시 문다 |
세 방식 모두 같은 곳에서 대가를 치른다. 여러 어댑터를 한 서버에 태우는 글에서 세어 본 것처럼 랭크 16짜리 8B 어댑터 하나가 fp16으로 34MB다. 서른 개면 1GB이고, 그 1GB는 KV 캐시에서 빼 온 자리다. 동시에 담을 수 있는 요청 수가 줄어 처리량이 떨어지는데, 지표에는 「처리량 하락」으로만 보이고 원인이 어댑터라는 것은 드러나지 않는다.
그래서 어댑터를 늘릴 때는 적재된 어댑터 수와 KV 캐시 여유를 같은 대시보드에 나란히 두는 것이 좋다. 둘을 따로 보면 연결이 안 보인다.
지연 적재를 쓸 때 자주 놓치는 것이 하나 더 있다. 첫 요청이 무는 수백 ms는 평균 지연에 거의 안 나타나지만 p99에는 그대로 찍힌다. 한산한 어댑터가 많을수록 「첫 요청」의 비율이 올라가므로, 어댑터 수를 늘리면 p99가 슬금슬금 나빠진다. 야간에 미리 한 번씩 불러 캐시를 데워 두는 것으로 대부분 없앨 수 있다.
바꿔 끼우는 순간
새 버전을 올릴 때 가장 나쁜 방법은 같은 이름으로 덮어쓰는 것이다. 진행 중인 요청이 중간에 다른 가중치를 만나고, 무엇보다 되돌릴 대상이 사라진다.
어댑터 이름은 불변으로 다룬다. support-ko@v3은 한 번 등록되면 내용이 바뀌지 않고, 고칠 것이 있으면 v4를 새로 만든다. 서버는 여러 버전을 동시에 들고 있고, 요청은 어느 것을 쓸지 지정한다.
# 요청이 버전까지 지정한다 — 서버 상태에 의존하지 않는다
resp = client.completions.create(
model="support-ko@v3",
prompt=prompt,
)
라우팅 규칙은 게이트웨이가 들고 있는 편이 낫다. 「support-ko로 온 요청 중 5%는 v4로」 같은 규칙을 추론 서버 안에 두면 서버를 재시작해야 바꿀 수 있는데, 되돌리기가 필요한 순간은 대개 재시작이 가장 하기 싫은 순간이다.
되돌리는 길을 먼저 만든다
배포 절차에서 실제로 가장 자주 쓰게 되는 것은 전진이 아니라 후진이다. 그런데 후진 경로는 대개 나중에 만들어진다.
어댑터는 이 점에서 운이 좋다. 새 버전과 이전 버전이 같은 서버에 함께 올라가 있으므로 되돌리기가 재배포가 아니라 라우팅 비율 변경이다. 베이스 가중치 16GB를 다시 내려받을 일이 없으니 초 단위로 끝난다. 이 성질을 살리려면 두 가지만 지키면 된다.
- 이전 버전을 내리지 않는다. 새 버전이 100%를 받은 뒤에도 이전 것을 최소 며칠 남겨 둔다. 어댑터 34MB를 아끼려고 되돌릴 길을 없애는 것은 남는 장사가 아니다
- 넘어가는 조건을 시간이 아니라 지표로 적는다. 「30분 뒤 50%로」가 아니라 「그 어댑터로 간 요청의 오류율이 기준 이하이고 p95가 기준 이하이면 50%로」다
단계를 나누는 이유는 관측 때문이다. 5%로 흘려 보는 동안 잡히는 것은 명백한 고장 — 형식이 깨지거나 빈 응답이 나오거나 지연이 튀는 것 — 이고, 품질 저하는 표본이 쌓여야 보인다. 그래서 5%에서는 오류율만 보고, 50%에서 지연과 품질을 함께 보고, 100%로 넘긴 뒤에 온라인 지표를 며칠 지켜본다.
무엇을 지켜볼 것인가
어댑터를 여럿 태우면 지표를 어댑터별로 갈라 봐야 한다. 전체 평균은 붐비는 어댑터의 숫자에 눌려서, 한산한 어댑터 하나가 완전히 망가져도 평균은 멀쩡해 보인다.
최소한 이 넷은 어댑터 이름과 버전을 라벨로 붙여 둔다.
- 요청 수와 오류율
- p50 · p95 지연 (첫 토큰까지와 전체를 나눠서)
- 적재 여부와 마지막 사용 시각 — LRU를 쓸 때 얼마나 자주 내렸다 무는지가 여기 보인다
- 출력 길이 분포 — 형식이 깨지면 길이부터 이상해지는 경우가 많다
마지막 것을 넣어 두면 회귀 테스트가 잡지 못한 것을 운영 중에 잡는다. 골든셋 100문제가 통과했다고 해서 실사용 분포 전체가 통과한 것은 아니기 때문이다.
정리
미세조정의 결과물은 파일이지만, 배포되는 것은 파일이 아니라 이름과 버전이 붙고 무엇으로 만들어졌는지가 기록된 물건이다. 그 껍데기가 없으면 어댑터가 셋만 넘어도 관리가 무너진다.
- 설정 파일의 베이스 이름을 리비전까지 박아 둔다 — 이름만으로는 같은 가중치임을 보장하지 못한다
- 이름은 불변으로 다루고 고칠 것이 있으면 새 버전을 낸다. 덮어쓰기는 되돌릴 대상을 지우는 일이다
- 적재 전략은 어댑터 수가 정하고, 어느 쪽이든 KV 캐시에서 자리를 빼 온다는 사실은 같다
- 되돌리기가 라우팅 변경으로 끝나도록 이전 버전을 남겨 둔다. 어댑터 배포가 컨테이너 배포보다 나은 점이 정확히 여기다
읽어주셔서 감사합니다. 😊

