로컬 모델은 출력에 필수 필드를 빠뜨렸고, 문서에 없는 숙박비를 답했다. 두 실패를 학습 부족으로 묶으면 다음 실험의 목적이 흐려진다. 필드 누락은 출력 규칙을 따르는 행동의 문제고, 숙박비 오답은 근거의 범위를 지키지 못한 문제인데 검색된 문서에 필요한 정보가 없었다는 사실도 함께 봐야 한다. 파인튜닝(fine-tuning)은 이미 학습된 모델의 일부 또는 전체 파라미터를 추가 데이터로 조정하는 일이고, 서비스를 고치는 여러 수단 중 하나다. 이 장의 결과물은 학습을 시작하는 코드가 아니라 학습을 고를 근거, 데이터 검사기, 비교 실험 계획이다. 가중치 학습은 실행하지 않았다.
지식 부족과 행동 불일치
학습된 파라미터에는 문서 저장소처럼 특정 정책의 개정 행을 바꾸거나 사용자별 읽기 권한을 거는 인터페이스가 없다. 비공개 정책을 학습한 뒤 출력 단계의 지시문으로 접근을 통제하는 설계도 피한다. 반대로 근거와 질문이 충분한데도 여러 사례에서 같은 필드를 빠뜨리거나, 정해진 분류 체계를 안 따르거나, 근거 부족 상태를 일관되게 표현하지 못하면 행동 적응을 검토할 수 있다. 그때도 원인은 여럿일 수 있다. 잘못 적용한 채팅 템플릿, 부족한 출력 토큰, 서로 충돌하는 지시, 기반 모델의 한계가 같은 증상을 만든다.
| 관찰한 실패 | 먼저 확인할 항목 | 다음 비교 후보 |
|---|---|---|
| 새 정책 대신 과거 금액을 답함 | 개정·시행일 필터와 실제 전송 문맥 | 데이터 갱신, 검색·문맥 수정 |
| JSON의 필수 필드가 빠짐 | 출력 한도, 템플릿, 스키마 전달 | 제약 생성, 예시 개선, 모델 교체, SFT |
| 인용은 있으나 답과 무관함 | 정답이 문서에서 나오는지 | 근거 판단 평가, 모델 교체, 행동 학습 |
| 초안 없이 등록을 시도함 | 서버의 행동 허용 목록 | 업무 제어 코드 수정 |
| 특정 문체만 반복해서 어긋남 | 지시의 모호함과 대표 예시 | 프롬프트 비교, 충분한 예제로 학습 |
SFT는 지도 파인튜닝(supervised fine-tuning)의 약자로 입력과 바람직한 응답의 쌍을 쓴다. 서버의 권한 검사나 멱등성 오류는 그 쌍을 늘려서 풀 수 없다. 모델이 어떤 요청을 내더라도 코드가 업무 규칙을 지켜야 한다.
대안 비교
프롬프트와 예시는 바꾸고 되돌리기 쉽지만 길어지면 입력 비용과 문맥 점유가 는다. 검색은 질문마다 바뀌는 사실을 공급하지만 모델이 그 사실을 제대로 쓰는 것까지는 별개다. 제약 생성은 지원되는 구조 안에서 출력을 제한하지만 유효한 숫자 필드에 틀린 금액을 넣는 의미 오류는 따로 검사해야 한다. 모델 교체는 데이터 준비 없이 능력을 비교할 수 있지만 배포 자원·지연·비용이 바뀐다. 학습은 반복되는 행동을 파라미터에 반영하는 대신 데이터 작성과 검토, 학습 실행, 회귀 평가, 모델 버전 관리 비용이 붙고, 기반 모델과 어댑터의 조합도 배포 대상이 된다. 짧아진 프롬프트만 보고 전체 비용이 줄었다고 볼 수 없다.
앞 실험의 v2 프롬프트는 v1 결과를 보고 고쳤다. 그 세 생성 사례는 이미 개발에 쓴 사례라서, 나중에 파인튜닝을 비교하더라도 독립 시험 성적으로 보고할 수 없다. 개발 실패를 고치는 데는 계속 쓰되 최종 판단에는 작성과 선택 과정에서 보지 않은 질문이 필요하다. 보류는 파인튜닝이 효과 없다는 결론과 다르다. 비용을 들이기 전에 실패 원인과 더 간단한 대안의 효과를 가르는 실험 순서다.
학습·개발·시험 분할
{
"id": "T01",
"split": "train",
"family": "education",
"question": "교육비 신청 전에 무엇을 해야 하나요?",
"evidence": {"s1": "교육비는 수강 전에 팀장의 승인을 받아야 한다."},
"target": {
"status": "answered",
"answer": "수강 전에 팀장의 승인을 받아야 한다.",
"citations": [{"source_id": "s1", "quote": "수강 전에 팀장의 승인을 받아야 한다."}],
"clarification": null
}
}
학습 데이터의 한 행에는 질문만 두지 않는다. 모델이 볼 근거, 기대하는 완전한 응답, 식별자, 분할 이름, 같은 출처에서 파생된 계열을 함께 적는다. fixtures/tuning-samples.jsonl은 교육용 내부 형식이고 특정 학습 서비스에 그대로 올리는 형식과 다르다.
같은 문의에서 파생된 대화 차례와 요약도 함께 묶는다. 아홉 행은 데이터 구조와 실패 검사만 보여 주는 표본이라 학습 준비가 끝난 데이터셋과 다르고, 저장소에 공개된 시험 행은 비공개 평가 자료와도 다르다. 독자는 자기 실험용 독립 자료를 새로 만든다.
books/ai-engineering/examples/.venv/bin/python books/ai-engineering/examples/tuning_data.py
samples: 9
families: 5
splits: dev=2, test=3, train=4
scope: structure_and_exact_input_leakage_only
검사기는 모든 행의 필드와 응답 상태를 보고, 인용 검사로 근거에 없는 인용을 거부하고, 같은 식별자의 중복, 서로 다른 분할에 걸친 계열, 질문과 근거가 같은 입력을 잡는다. 동일 입력 비교는 유니코드 NFC와 공백을 정규화하고 근거의 식별자·배열 순서를 빼므로 s1을 renamed로 바꿔도 누수를 숨길 수 없다. 정규화한 입력의 해시는 중복 비교용이고 개인정보 익명화 수단과 다르다. 바꿔 쓴 근접 중복, 잘못 표시한 계열, 문서 밖의 정답 유출은 이 검사로 못 찾는다. 답변의 뜻도 판정하지 않아서 “사전 승인이 필요하다”를 정확히 인용하면서 “승인 없이 신청할 수 있다”고 답한 표본도 구조 검사는 통과한다. 테스트가 이 한계를 고정한다. 자동 검사 통과와 정답 검토 완료는 다른 상태다. 실제 데이터에는 사용 권한, 개인정보 최소화, 정답 근거, 검토자 판단과 이견 처리가 더 필요하고, 운영 로그를 자동으로 정답으로 올리거나 사용자의 민감한 내용을 학습 파일에 복사하지 않는다.
SFT와 LoRA
지도 학습은 입력을 조건으로 정답 응답의 토큰이 나올 확률을 높인다. 입력 , 정답 토큰열 에 대한 대표적인 손실은 다음과 같다.
학습 시 각 위치에는 앞서 생성한 오답 대신 정답의 앞부분을 조건으로 준다. 손실이 내려갔다는 것은 그 조건에서 정답 토큰의 확률이 올랐다는 뜻이고, 추론에서는 모델 자신의 출력이 다음 입력이 되므로 학습 손실만으로 자유 생성의 성공을 판정할 수 없다. 채팅 데이터에서는 어느 토큰에 손실을 적용할지도 정한다. 질문과 검색 문맥은 입력으로 쓰고 응답만 학습 대상으로 삼으려면 입력 토큰에 손실을 적용하지 않는 마스크가 필요하다. TRL의 SFT 문서는 completion-only와 assistant-only 학습 설정을 구분하는데, 선택한 버전·데이터 형식·채팅 템플릿이 의도한 마스크를 만드는지 토큰 수준에서 확인한다. JSONL을 학습 입력으로 바꾼다면 서비스와 같은 지시문·근거 직렬화·채팅 템플릿을 쓰고 target을 완전한 JSON 응답으로 직렬화한다. 학습에서는 누락 필드를 허용하고 추론에서는 필수로 요구하는 식의 차이를 만들지 않고, 너무 긴 표본을 조용히 자르지 않는다. TRL SFT Trainer
전체 파인튜닝은 모델의 전체 파라미터를 갱신하고, LoRA는 기반 가중치를 고정한 채 저랭크 행렬로 표현한 변화량을 학습한다. 실제 구현에는 배율과 적용 모듈 설정이 더 있다. QLoRA는 고정된 기반 모델을 4비트로 두고 어댑터를 학습하며 NF4, 이중 양자화, 페이지드 옵티마이저를 쓴다. “전체 학습이 4비트로 된다”로 요약하면 필요한 메모리와 수치 동작을 오해하기 쉽다. 추론에 모델이 올라간 메모리로는 학습에 부족하다. 활성값, 기울기, 옵티마이저 상태, 임시 버퍼가 더 들고 배치 크기와 시퀀스 길이가 영향을 준다. LoRA도 기반 모델의 순전파와 필요한 역전파를 없애지 못한다. 이 책의 CPU 환경에서는 LoRA·QLoRA 학습을 실행하지 않았으므로 배치 크기, 학습 시간, 향상률은 없다. 심화 실험을 한다면 기반 모델 리비전, 라이선스, 토크나이저·템플릿, 학습 코드 버전, 데이터 해시, 분할, 난수 시드, 어댑터 대상과 랭크, 학습률, 스텝, 최종 체크포인트를 함께 보관한다. LoRA 원 논문, QLoRA 원 논문
개선·회귀·비용
| 평가 축 | 기록할 결과 | 판단 시 주의점 |
|---|---|---|
| 출력 형식 | 완결된 생성 수, 스키마·상태 검사 통과 수 | 거부된 응답을 분모에서 빼지 않는다 |
| 근거 정확성 | 답의 각 주장에 대한 문서 지지 여부 | 인용 문자열의 존재와 구분한다 |
| 근거 부족 대응 | 답할 수 없는 질문의 적절한 보류 | 모두 보류해서 점수를 얻지 않게 답변 가능 사례도 평가한다 |
| 권한·업무 규칙 | 무권한 접근과 승인 없는 실행 시도·실제 실행 | 모델 행동과 서버 차단 결과를 따로 적는다 |
| 회귀 | 기존 정상 질문, 상충·개정·공격 사례의 변화 | 전체 평균이 좋아져도 중요한 실패를 숨기지 않는다 |
| 비용·지연 | 학습·검토·배포 비용, 요청당 자원, 지연 분포 | 실패·재시도·운영 부담을 넣는다 |
학습 도입 실험은 시작 전에 비교 대상과 채택 조건을 적는다. 기존 프롬프트, 개선 프롬프트 또는 제약 생성, 대체 모델, 학습 후보를 같은 입력과 같은 서비스 검증기로 평가한다. 후보마다 다른 필수 필드나 인용 규칙을 적용하면 비교가 무너진다. 표본이 적으면 성공률 하나보다 성공 건수와 전체 건수, 실패 내용을 함께 읽는다. 3건 중 3건 통과는 그 세 사례의 관찰이다. 필수 필드 누락은 줄었는데 근거 없는 금액 답변이 늘었다면 채택을 보류하고, 학습 데이터가 답변 사례에 치우쳤는지, 보류 사례의 정답이 일관되는지, 에포크가 개발 손실만 낮췄는지 본다.
비용은 일회성 데이터 검토·학습 비용과 지속적인 추론 비용을 나눈다. 초기 추가 비용이 , 요청당 절감액이 이면 단순 회수 요청 수는 다. 정기 재학습, 평가, 배포 유지 비용은 여기 없으니 따로 더한다. 품질 기준을 넘지 못한 후보에는 이 계산으로 도입을 정당화하지 않는다. 이번 실행은 데이터 규칙과 분할 검사까지고 학습 전후 비교는 없다. 현재 결정은 학습 보류다.
실습: 분할과 검사기 확장하기
test_tuning_data.py를 돌린다. 계열 누수, 근거 별칭을 바꾼 동일 입력, 잘못된 인용, 필수 필드 누락, 분할 부재, 중복 식별자를 거부하는지와, 뜻이 틀린 정답이 통과하는 사례가 그대로 있는지 본다.- 가상의 새 정책 계열을 추가하고 어느 분할에 넣을지 먼저 정한다. 같은 정책을 바꿔 쓴 질문은 그 계열과 함께 둔다.
- 답변 가능·근거 부족·추가 질문·상충 자료 사례의 정답을 쓰고, 자동 검사 결과와 사람의 근거 검토를 각각 기록한다. 모델 비교에 쓴 자료는 개발 자료로 표시한다.
이 장의 완료 기준은 학습 작업의 실행 여부와 무관하다. 실패 원인을 나누고, 학습이 바꿀 행동을 정의하고, 분리된 자료를 검사하고, 개선·회귀·비용을 함께 판단할 수 있으면 된다.