콘텐츠로 이동

3-2. 모델 서빙 구조와 실행 단위

모델 서빙 구조는 요청이 들어와 점수(score), 임계값(threshold), 예측(prediction), 로그로 이어지는 실행 경로입니다. 3-2에서는 코드를 모두 외우는 것이 아니라, 품질 문제가 생겼을 때 모델 파일, API 스키마(schema), 특성(feature) 정렬, 모델 로더, 설정값 중 어디를 확인해야 하는지 나누어 봅니다.

이 문서를 읽을 때는 다음 기준을 중심으로 확인합니다.

  • 모델 파일과 실행 환경 분리: 모델 산출물(model artifact) 문제와 API 실행 문제를 구분
  • 요청 처리 경로: API 스키마, 내부 요청 객체, 특성 정렬, 모델 로더, 유스케이스(use case)의 책임 확인
  • 출력 생성 위치: 점수, 임계값, 예측, 모델 버전(model_version)이 어디서 만들어지는지 확인
  • 설정값 위치: 파일 설정과 환경 변수 중 실제 실행값을 어디서 확인해야 하는지 구분

3장의 상황에서 3-2가 확인하는 것은 “정상 응답이 어떤 조건에서 만들어졌는가”입니다. API가 200 OK를 반환했더라도 모델 산출물, 입력 스키마, 특성 순서, 모델 로더, 임계값 적용 위치를 나누어 볼 수 있어야 prediction이 어떤 기준에서 생성되었는지 설명할 수 있습니다.

3-2-1. 모델 파일과 실행 환경의 분리

모델 파일은 학습 결과입니다. 보통 pickle, joblib, ONNX, 또는 framework별 저장 형식으로 저장됩니다. 반면 API 서버는 모델 파일을 읽어 요청마다 점수를 계산하고 응답을 반환하는 실행 환경입니다. 두 가지를 구분해야 모델 버전 문제와 실행 환경 문제를 나눠서 추적할 수 있습니다.

같은 API 실패나 예측 분포 변화도 원인 영역을 나누어 봐야 정확히 대응할 수 있습니다. 예를 들어 API가 500 오류를 반환한다면 모델 파일을 읽지 못한 문제일 수 있고, 요청 스키마가 맞지 않는 문제일 수도 있고, 모델 입력 특성이 부족한 문제일 수도 있습니다. 반대로 API가 정상 응답을 반환하지만 운영에서 예측 분포(prediction distribution)가 달라졌다면 모델 산출물, 임계값, 특성 처리, 입력 데이터 분포를 나누어 확인해야 합니다.

같은 “API 응답 이상”이라도 확인할 위치는 다릅니다. 아래 표는 3장에서 반복해서 사용할 상황을 서빙 구조 관점으로 나눈 것입니다.

관측된 현상 먼저 확인할 위치 이유
/predict가 500 오류 반환 모델 로더, 모델 경로(model path), 모델 산출물 모델을 읽지 못하면 점수 계산 전 단계에서 실패
/predict는 200이지만 model_version 누락 응답 스키마, 응답 생성 코드 추적 정보가 없으면 배포 버전과 연결 불가
score는 있으나 prediction 비율이 급변 임계값, 특성 순서, 입력 데이터 분포 모델 자체보다 운영 기준이나 입력 변화 가능성 존재
오류 입력이 500으로 처리 FastAPI/Pydantic 입력 스키마 잘못된 입력이 예측 로직 전에 차단되지 않음

이 현상을 코드 구조로 옮기면 다음 네 가지 구성 요소를 분리해서 볼 수 있습니다.

구성 책임 QA 확인
모델 산출물 점수 계산에 사용되는 학습 결과 의도한 모델 버전인지
모델 로더 모델 파일을 메모리로 읽기 로딩 실패와 경로 오류가 없는지
예측 API HTTP 요청 수신과 특성 검증 입력 스키마와 오류 응답이 명확한지
설정값 threshold, model_version, model_path를 제공 평가 기준과 서빙 기준이 일치하는지

이 분리는 코드 구조를 읽을 때도 중요합니다. 실습 코드는 확인 지점을 나누기 위해 도메인(domain), 애플리케이션(application), 인프라스트럭처(infrastructure)를 분리합니다. 도메인과 애플리케이션에는 예측 요청, 응답, 임계값 적용처럼 품질 판단에 가까운 흐름을 두고, FastAPI나 scikit-learn 로더 같은 도구 의존성은 인프라스트럭처에 둡니다. 수강생이 이 구조를 깊게 설계할 필요는 없지만, 문제가 생겼을 때 어느 위치를 확인해야 하는지는 알아야 합니다.

3장의 상황에 적용하면, API 응답이 정상이어도 바로 “모델이 평가와 같은 조건으로 동작한다”고 결론내리지 않습니다. 먼저 모델 파일이 2장에서 평가한 기준선인지, 입력 스키마가 학습 특성을 모두 받는지, 설정값이 응답과 로그에 남는지 확인합니다.

3-2-2. 예측 API, 모델 로더, 특성 정렬의 역할

예측 API는 HTTP 요청을 입력 스키마로 검증하고, 모델 로더는 scikit-learn 모델을 읽고, 유스케이스는 점수를 예측으로 바꿉니다. 특성 정렬은 API 페이로드(payload)를 모델이 학습 때 사용한 컬럼 순서로 맞추는 단계입니다. 구조는 packages/ai-quality/src/ai_quality/serving 아래에 분리되어 있습니다.

계층 파일 역할
domain prediction_request.py 내부 요청 객체
domain prediction_response.py 내부 응답 객체
application predict_risk.py score, threshold, prediction 생성
infrastructure fastapi_app.py FastAPI route 제공
infrastructure sklearn_model_loader.py 모델 산출물 로딩과 특성 순서 적용

QA가 이 구조를 알아야 하는 이유는 문제 발생 시 확인 지점을 나누기 위해서입니다. 요청이 스키마 검증에서 실패하면 FastAPI 입력 모델을 봐야 합니다. 모델 파일 로딩이 실패하면 모델 경로와 모델 산출물을 확인해야 합니다. 점수는 계산되지만 예측이 기대와 다르면 특성 순서와 임계값 설정을 확인해야 합니다.

각 계층은 다음 책임을 갖습니다.

책임 확인 위치 실패 예시
요청 형식 검증 FastAPI 스키마 필수 필드(field) 누락, 타입 오류
내부 요청 생성 domain request 특성 이름 대응 오류
모델 로딩 sklearn loader 모델 파일 누락, 버전 불일치
점수 계산 scikit-learn loader와 application 유스케이스 특성 순서 오류, 모델 입력 구성 오류
예측 생성 application 유스케이스 임계값 설정 오류
응답 반환 응답 스키마 model_version 누락

이 구조를 문서에 보여주는 이유는 수강생이 전체 코드를 모두 외우게 하려는 것이 아닙니다. AI 서비스 품질 문제를 만났을 때 “어디를 확인해야 하는가”를 알 수 있게 하기 위해서입니다. 특히 3장에서는 PredictionPayload, PredictionRequest, SklearnScoringModel, PredictRisk가 어떤 순서로 연결되는지 확인하면 충분합니다.

3-3 Lab에서는 이 구조를 실제 API 호출로 확인합니다. 정상 요청에서는 score, threshold, prediction, model_version이 응답에 포함되는지 보고, 오류 요청에서는 잘못된 입력이 예측 로직으로 들어가기 전에 검증 오류(validation error)로 차단되는지 확인합니다.

3-2-3. 점수, 임계값, 예측 생성 위치

핵심 유스케이스는 packages/ai-quality/src/ai_quality/serving/application/predict_risk.py에 있습니다. 이 코드는 요청 특성을 모델에 전달해 점수를 받고, 임계값을 적용해 최종 예측을 만듭니다.

아래 코드는 핵심 흐름만 발췌한 것입니다. 실제 구현에서는 응답을 만든 뒤 예측 이벤트 로그도 함께 남깁니다.

def run(self, request: PredictionRequest) -> PredictionResponse:
    """Return prediction response for one request."""
    score = self.model.score_one(request.features)
    prediction = POSITIVE_LABEL if score >= self.threshold else NEGATIVE_LABEL
    response = PredictionResponse(
        request_id=request.request_id,
        model_version=self.model_version,
        score=score,
        threshold=self.threshold,
        prediction=prediction,
    )

    if self.event_sink is not None:
        self.event_sink.record(response)

    return response

QA는 이 흐름에서 scorethreshold가 모두 응답 또는 로그에 남는지 확인해야 합니다. prediction만 남기면 임계값 변경으로 인한 품질 변화를 추적하기 어렵습니다. 예를 들어 어제는 score=0.62high_risk였는데 오늘은 같은 scorelow_risk가 되었다면 임계값 변경 가능성을 확인해야 합니다.

의미 QA 활용
score 관심 클래스(Positive class) 가능성 점수 점수 분포(score distribution)와 임계값 근처 샘플(sample) 확인
threshold 점수를 클래스(class)로 바꾸는 기준 FP/FN 변화 원인 추적
prediction 최종 예측 클래스 예측 분포 확인
model_version 사용한 모델 식별자 배포 버전 확인

점수와 예측의 차이를 이해하는 것은 4장 운영 관측과 5장 품질 전략의 기반입니다. 운영 중 예측 비율이 바뀌었을 때 점수 분포가 먼저 바뀐 것인지, 임계값 설정이 바뀐 것인지 구분하려면 두 값을 모두 기록해야 합니다.

3-2-4. 설정값과 모델 버전의 위치

서빙 설정은 configs/operations/serving.yaml에 있습니다. 환경 변수로 덮어쓸 수 있으므로 Docker/Kubernetes에서도 같은 코드를 사용합니다. QA는 “파일에 적힌 값”과 “실행 중인 API가 실제로 반환하는 값”을 구분해야 합니다.

설정 실습 기본값 QA 확인
MODEL_PATH artifacts/models/chapter_02_baseline.pkl 2장에서 평가한 기준선(baseline) 모델인지
MODEL_VERSION v1 응답과 로그의 모델 버전이 배포 기록과 맞는지
MODEL_THRESHOLD 0.5 평가 기준 임계값과 실행 중 임계값이 같은지
EVENT_LOG_PATH artifacts/logs/prediction_events.jsonl 예측 이벤트 로그가 남는 위치를 추적할 수 있는지

설정값은 코드보다 자주 바뀔 수 있습니다. 모델 파일 경로, 임계값, 모델 버전은 배포 환경에서 변경될 수 있으므로 QA는 소스 코드만 보고 판단하면 안 됩니다. 실제 실행 중인 API 응답과 로그에서 설정값을 확인해야 합니다.

특히 MODEL_VERSION은 사람이 읽기 쉬운 식별자이지만, 그것만으로 모델 산출물이 맞다고 보장되지는 않습니다. 가능하면 모델 경로, 이미지 태그(image tag), 실험 기록(run) ID까지 함께 연결하는 것이 좋습니다. 실습에서는 복잡도를 줄이기 위해 model_versionthreshold 확인에 집중합니다.