구조화 출력 모델을 운영하다 보면 이상한 실패가 반복됩니다. JSON이 깨지는 정도가 아니라 같은 문장을 계속 반복하거나, OCR 결과가 루프에 빠지거나, 특정 필드를 끝없이 복사하는 식입니다. temperature를 낮추고 repetition penalty를 넣어도 완전히 사라지지 않습니다. Hugging Face에 공개된 Dharma AI의 글은 이 문제를 Direct Preference Optimization, 즉 DPO로 다루는 실전 사례를 보여줍니다. 핵심은 실패 출력을 버리지 않고 rejected example로 사용했다는 점입니다.
DharmaOCR 사례에서 SFT 이후 DPO를 추가하자 text degeneration이 모든 모델 패밀리에서 줄었습니다. 평균 감소율은 59.4%, 가장 큰 케이스는 87.6%였다고 설명합니다. 이 숫자보다 중요한 것은 방법론입니다. DPO는 챗봇의 선호도 정렬에만 쓰는 도구가 아니라, OCR이나 structured generation처럼 정답 기준이 명확한 작업의 실패 모드를 직접 겨냥할 수 있습니다.
Supervised Fine-Tuning은 정답 출력을 더 잘 따라 하게 만듭니다. 문서 OCR이라면 올바른 전사 결과, JSON 추출이라면 맞는 필드 구조를 학습합니다. 문제는 SFT의 목표가 토큰 단위 likelihood를 높이는 데 있다는 점입니다. 모델이 반복 루프에 빠졌을 때 그 전체 completion이 실패라는 신호를 직접 받지 못합니다.
반복 루프는 단순한 decoding 설정 문제가 아닐 수 있습니다. 특정 토큰이나 패턴이 자기 자신을 강화하는 분포 영역에 들어가면, 다음 토큰 예측이 계속 같은 방향으로 쏠립니다. temperature, top-p, repetition penalty는 증상을 줄일 수 있지만, 모델이 그 실패 출력을 나쁜 completion으로 학습한 것은 아닙니다.
DPO는 여기서 다른 신호를 줍니다. 같은 입력에 대해 chosen output과 rejected output을 만들고, 모델이 chosen을 선호하도록 학습합니다. 구조화 작업에서는 사람 취향이 아니라 명확한 품질 기준이 있습니다. 올바른 OCR은 chosen, 반복 루프는 rejected입니다.
많은 데이터 정제 파이프라인은 모델이 만든 이상한 출력을 제거합니다. 깨끗한 데이터셋을 만들기 위해서입니다. 하지만 반복 루프를 줄이는 목적이라면 이 출력은 노이즈가 아니라 가장 중요한 학습 신호입니다. DharmaOCR 접근은 SFT 모델이 실제로 만들어낸 degenerate output을 rejected example로 보존했습니다.
이 방식의 장점은 실패가 모델 자신의 분포에서 나온다는 점입니다. 사람이 상상한 나쁜 예시보다 실제 모델이 빠지는 함정이 더 정확한 negative signal입니다. 운영 중인 모델이 특정 영수증 양식에서 금액 필드를 반복한다면, 그 실패 출력을 모아 rejected set으로 쓰는 편이 일반적인 오류 예시를 만드는 것보다 낫습니다.
첫 번째 후보는 OCR과 문서 추출입니다. 계약서, 세금계산서, 영수증, 의료 문서처럼 출력 구조가 명확하고 정답 비교가 가능한 영역입니다. 반복 루프, 필드 누락, 잘못된 복사, 포맷 붕괴를 rejected output으로 만들 수 있습니다.
두 번째는 코드 생성의 특정 하위 작업입니다. 예를 들어 TypeScript type 생성, SQL migration 생성, OpenAPI schema 변환처럼 정답 구조가 비교적 명확한 작업입니다. 모델이 같은 필드를 반복하거나, import를 무한히 추가하거나, JSON schema nesting을 망치는 경우를 negative pair로 만들 수 있습니다.
세 번째는 RAG 답변의 근거 일치 검증입니다. 정답 문서에 없는 내용을 섞은 답변, 같은 문장을 반복한 답변, 출처 ID를 잘못 붙인 답변을 rejected output으로 구성할 수 있습니다. 단, 이 경우 chosen과 rejected의 품질 차이를 자동으로 안정적으로 판정할 수 있어야 합니다.
절차는 단순하게 시작할 수 있습니다. 먼저 SFT 또는 현재 운영 모델로 같은 입력에 대해 여러 후보 출력을 생성합니다. 다음으로 자동 judge나 규칙 기반 검사로 출력 품질을 분류합니다. 반복 루프는 n-gram repetition, 특정 토큰 비율, 최대 길이 도달 여부, schema validation 실패로 잡을 수 있습니다. 그다음 같은 입력에 대해 좋은 출력과 나쁜 출력을 묶어 preference pair를 만듭니다.
여기서 주의할 점은 rejected output을 너무 다양하게 섞지 않는 것입니다. 반복 루프를 줄이려는 DPO라면 반복 루프 중심으로 negative set을 구성해야 합니다. 필드 누락, 오탈자, hallucination까지 한 번에 다 잡으려 하면 학습 신호가 흐려질 수 있습니다. 실패 모드별로 데이터셋을 나누고, 가장 비용이 큰 실패부터 줄이는 편이 낫습니다.
DPO는 모델을 개선하는 방법이지 런타임 검증을 대체하지 않습니다. 구조화 출력 서비스에는 여전히 schema validation, retry, fallback, human review가 필요합니다. 특히 문서 추출 결과가 결제, 보험, 세무, 의료 판단에 연결된다면 모델 출력만 믿으면 안 됩니다.
추천 구조는 세 단계입니다. 첫째, 런타임에서 반복 루프와 schema 오류를 탐지합니다. 둘째, 실패 샘플을 로그로 모아 재현 가능한 데이터셋을 만듭니다. 셋째, 일정량이 쌓이면 DPO 또는 다른 preference optimization으로 실패 모드를 학습시킵니다. 이렇게 하면 운영 로그가 단순 장애 기록이 아니라 모델 개선 재료가 됩니다.
DPO의 실전 가치는 “좋은 답변을 더 좋게”보다 “반복되는 나쁜 실패를 명시적으로 피하게” 만드는 데 있습니다. 구조화 출력, OCR, 코드 변환, RAG 검증처럼 품질 기준이 명확한 작업이라면 실패 출력을 버리지 말고 학습 신호로 바꿔볼 만합니다.