콘텐츠로 이동

파인튜닝 패스스루: 설계 기록

이 문서는 /v1/fine_tuning/jobs 패스스루의 설계를 기록합니다 (이슈 #2358). 경로는 src/http/handlers/fine_tuning.rs와 src/services/fine_tuning.rs에 구현되어 있으며, 이 페이지는 구현이 따르는 설계를 기록합니다.

구현된 표면

/v1 표면은 chat, completions, responses, embeddings, rerank, images, audio, Anthropic Messages, files, tokenize, models, realtime, batches(control-plane 기능을 컴파일한 경우에만), 그리고 아래의 파인튜닝 작업 경로를 다룹니다. 작업 경로는 모든 빌드에 마운트됩니다.

메서드 경로 동작
POST /v1/fine_tuning/jobs 요청의 model로 선택한 백엔드에 작업을 생성합니다
GET /v1/fine_tuning/jobs 파인튜닝을 지원하는 백엔드 하나에서 호출자의 작업 목록을 조회합니다
GET /v1/fine_tuning/jobs/{job_id} 작업을 생성한 백엔드에서 작업을 조회합니다
POST /v1/fine_tuning/jobs/{job_id}/cancel 작업을 생성한 백엔드에서 작업을 취소합니다

각 응답은 백엔드의 상태 코드, 콘텐츠 타입, 본문을 반환합니다. 목록 본문은 바이트 단위 그대로 전달하는 예외이며, data 배열에는 호출자 자신의 작업만 남습니다(작업 바인딩 참고).

정책 계층

작업 CRUD는 채팅 디스패치가 아닙니다. 라우터는 모든 파인튜닝 호출에 API 라우트 그룹의 인증, 전역, 클라이언트별, 키별 속도 제한을 적용하고, 생성과 목록에는 백엔드별 디스패치 한도를 청구하며, completion 페이로드를 전제로 하는 계층은 의도적으로 실행하지 않습니다.

  • 요청 파라미터 정책: 적용하지 않습니다. 이 정책은 완성도 샘플링과 도구 필드를 재작성하는데, 작업 페이로드에는 그런 필드가 없습니다.
  • 가드레일: 적용하지 않습니다. 입력 및 출력 게이트는 메시지와 완성 텍스트를 판정하며, 라우터는 학습 데이터를 보지 않습니다.
  • 응답 캐싱: 적용하지 않습니다. 캐시된 목록이나 생성 응답은 작업 상태 변경을 숨깁니다.
  • 모델별 생성 예산: 청구하지 않습니다. 작업 호출은 토큰을 생성하지 않으며, 청구하면 제어 평면 트래픽에 모델의 완성 예산을 쓰게 됩니다.
  • 측정: non-completion 요청으로 적용합니다. 응답에 셀 usage 블록이 없으므로, 라우터는 요청 수, 지연 시간, 키 및 백엔드 귀속을 0 토큰(token_source: none)으로 기록합니다.

설계

  • 백엔드 범위: http/https의 OpenAI-wire 타입만 해당합니다(openai, generic). Azure OpenAI는 파인튜닝 엔드포인트 경로가 다르고 api-version이 필수이므로 제외합니다. Fine Tuning API를 서빙하지 않는 백엔드는 선택 전에 후보 집합에서 제외되며, 지원 백엔드가 하나도 없는 요청은 라우터 수준 404가 아니라 구조화된 400 fine_tuning_not_supported를 반환합니다.
  • 백엔드 선택: 가장 가까운 기존 형태는 /v1/batches(#880)이지만, 이는 허브 조건이 붙은 프로바이더 배치 디스패치이며 라우터 측 추적기를 사용합니다(src/control_plane/batch/mod.rs). 파인튜닝은 허브 조건 없이 같은 라이프사이클 규칙을 따릅니다. 생성은 요청의 model로 백엔드를 선택하고, 목록은 호출자가 접근할 수 있는 지원 백엔드 하나를 선택하며, 조회와 취소는 바인딩을 사용합니다.

작업 바인딩

생성 응답은 작업 ID를 읽을 수 있을 만큼만 버퍼링합니다. 그다음 라우터는 (주체, 작업 ID) -> (백엔드, 모델)을 AppState별 LRU 테이블에 기록합니다. 이는 Responses 패스스루 테이블(services::responses::PassthroughRoutes, 이슈 #1941)과 같은 형태입니다. 조회와 취소는 이 바인딩을 찾은 뒤 실행 중인 설정과 호출자의 allowed_backends에 대해 다시 검사합니다. 알 수 없거나 다른 주체의 ID는 404 fine_tuning_job_not_found를 반환하며, 업스트림으로 아무것도 전송하지 않습니다.

GET /v1/fine_tuning/jobs도 같은 소유권 규칙을 적용합니다. 프로바이더의 목록에는 같은 자격 증명으로 이 라우터를 통해 다른 주체가 만든 작업을 포함해 백엔드 계정의 모든 작업이 담기므로, 라우터는 이 호출자에게 바인딩한 ID의 항목만 남깁니다. /v1/batches가 프로바이더 목록을 소유자별로 거르는 것과 같은 방식입니다. 따라서 이 라우터가 바인딩하지 않은 작업(재시작 전이나 다른 라우터 프로세스에서 생성한 작업)은 목록에 나오지 않으며, 같은 작업에 대한 조회와 취소가 not-found를 반환하는 것과 일치합니다.

바인딩 테이블은 프로세스 메모리에 있습니다. 라우터를 재시작하기 전에 생성한 작업은 백엔드에 남아 있지만, 이 라우터는 어느 백엔드를 호출해야 하는지 더 이상 알지 못하므로 not-found를 반환합니다. 이는 아래의 열린 질문입니다.

학습 파일

작업의 training_file과 validation_file ID는 선택된 백엔드에 존재해야 합니다. 라우터 자체 /v1/files는 업로드를 로컬 디스크(files.storage_path)에 저장하고 추론 요청에 해석해 넣을 뿐, 프로바이더에 업로드하지 않습니다. 따라서 라우터 파일 ID는 프로바이더 학습 파일이 아니며, 이 설계에는 프로바이더 업로드 단계가 없습니다.

남은 열린 질문

  • 작업 상태 영속화: 재시작 후 라우터 측 작업 상태를 어떻게 저장하고 복구할지. 메모리 바인딩은 프로세스 수명 동안만 소유권을 검증합니다. 배치 추적기처럼 영속화하면 조회와 취소가 재시작 후에도 동작합니다.
  • 학습 파일 업로드: 학습 파일을 프로바이더 파일 패스스루로 업로드할지, 라우터 파일 ID를 프로바이더 업로드에 매핑할지.
  • 파인튜닝된 모델 목록: 파인튜닝된 모델 목록을 GET /v1/models에 병합할지, 별도 패스스루 경로로 유지할지. 이번 구현에는 포함되지 않습니다.
  • 목록 팬아웃: GET /v1/fine_tuning/jobs가 백엔드 하나를 선택하는 대신 모든 지원 백엔드의 페이지를 병합해야 하는지. 병합은 정규화된 작업 스키마를 만들어야 하며, 이슈는 라우터가 그렇게 하지 말라고 요청합니다.