트레이너가 할 일 3가지
지표 기록, pyfunc.log_model 한 번, 종료 코드, 그리고 MLflow 사용 규칙과 SIGTERM 처리
나머지는 전부 플랫폼이 합니다. 트레이너는 데이터를 내려받지 않고, MLflow run 을 만들지 않고, 모델을 등록하지 않고, 표준 출력 규약을 지키지 않습니다.
| # | 항목 | 할 일 |
|---|---|---|
| 1 | 지표 | mlflow.log_metrics({...}, step=epoch) 또는 mlflow.autolog(). step 은 epoch. 총 epoch 수는 mlflow.log_param("epochs", N) 으로 |
| 2 | 모델 | mlflow.pyfunc.log_model(...) 을 한 번. 레지스트리 등록은 플랫폼이 합니다 |
| 3 | 종료 | 성공 0, 실패 0 이외. 중단(SIGTERM) 때는 가능하면 체크포인트를 남기고 종료합니다. 이때 종료 코드는 무엇이든 됩니다 |
1. 지표: MLflow 에만
import mlflow
with mlflow.start_run(): # MLFLOW_RUN_ID 에 자동으로 이어진다
mlflow.log_param("epochs", EPOCHS) # 진행률 분모
for epoch in range(EPOCHS):
loss, acc = train_one_epoch(...)
mlflow.log_metrics({"train/loss": loss, "eval/accuracy": acc}, step=epoch)
-
step은 epoch 입니다. 화면 곡선의 x 축이 이 값입니다. -
진행률은
max(metric.step) + 1 / params.epochs로 계산됩니다. paramepochs가 없으면 진행률 막대 없이 "학습 중" 으로만 보입니다(학습은 막지 않습니다). -
지표 이름은 자유입니다. 서버가 이름 패턴으로 우선순위를 매겨 기본 곡선 4개를 고르고, 나머지는 선택지로 보여 줍니다. 우선순위는
metrics/*>fitness>eval/*>val/*loss>train/*loss> 그 밖 >lr/*입니다. -
프레임워크가 이미 MLflow 콜백을 갖고 있으면(ultralytics 등) 그것으로 충분합니다.
-
학습이 끝난 뒤 더 남길 값이 있으면 run 이 이미 닫혔을 수 있으니, run id 를 받는 클라이언트로 씁니다. 닫힌 run 에도 기록됩니다.
from mlflow import MlflowClient MlflowClient().log_metric(run_id, "eval/mAP50", 0.71, step=0) -
지표 기록이 실패했다고 학습을 죽이지 마세요. 예외를 잡아 로그만 남기는 편이 낫습니다.
2. 모델: pyfunc.log_model 한 번
모델 로깅만 유일하게 형식이 정해져 있습니다. 가중치 파일(.pt)만 아티팩트로 올리면 서빙 이미지를 만들 수 없습니다. MLmodel 파일이 없으면 레지스트리에 있는 모델을 서빙할 수 없습니다.
info = mlflow.pyfunc.log_model(
name="model",
python_model="predictor.py", # 서빙용 predict() 래퍼 (models-from-code)
artifacts={"weights": "/geo/work/best.pt"},
signature=signature, # 사실상 필수. 없으면 서빙 요청이 실패한다
pip_requirements=[ # 학습 환경 버전 그대로 고정
f"torch=={torch.__version__.split('+')[0]}",
"mlflow==3.13.0", # 플랫폼 고정 버전
],
metadata={"input_kind": "image_b64"}, # 추론 콘솔이 입력 위젯을 고르는 힌트 (선택)
)
| 인자 | 빠지면 |
|---|---|
python_model | 서빙 컨테이너에는 학습 코드가 없습니다. predictor.py 한 파일이 모델 구조·가중치 로드·추론을 스스로 끝내야 합니다. 파일 끝에 mlflow.models.set_model(...) 이 있어야 로드됩니다 |
artifacts | 가중치가 모델에 실리지 않아 서빙 쪽에 로드할 파일이 없습니다 |
signature | 입력이 모두 float64 로 바뀌어 요청이 실패합니다. 컬럼 이름·dtype 이 곧 서빙 API 계약입니다 |
pip_requirements | 서빙 이미지가 이 목록으로 환경을 만듭니다. 학습과 버전이 다르면 체크포인트가 로드되지 않습니다 |
metadata.apt_packages | pip 로 안 되는 시스템 패키지(예: OpenCV 의 libgl1)가 빠져 서빙 컨테이너가 뜨지 않습니다 |
metadata 에서 플랫폼이 읽는 키:
| 키 | 읽는 곳 | 뜻 |
|---|---|---|
input_kind | 추론 콘솔 | 입력 위젯(image_b64 · tabular · timeseries …). 컬럼 이름으로 짐작하는 것보다 우선합니다 |
apt_packages | 서빙 이미지 빌더 | 서빙 이미지에 설치할 시스템 패키지 |
class_names | 결과 오버레이 | 인덱스 → 이름 |
preprocessing | 추론 콘솔 | 정규화 레시피. 모델이 스스로 전처리하면 적지 않습니다. 적으면 콘솔이 전처리를 한 번 더 합니다 |
등록은 플랫폼이 합니다
학습이 성공하면 플랫폼이 그 run 에 로깅된 모델을 찾아 레지스트리에 새 버전으로 등록하고 Staging 으로 올립니다. 모델 이름은 학습을 제출할 때 화면에서 정합니다. 트레이너는 register_model() 을 부르지 않습니다. 부르면 같은 모델의 버전이 둘 생깁니다.
등록이 실패해도 학습은 완료로 남습니다. 사유는 모델 레지스트리 등록 단계에 표시됩니다.
3. 종료 코드와 중단
| 컨테이너 종료 | 학습 상태 |
|---|---|
코드 0 | 완료 |
| 그 밖의 코드 | 실패. 로그 끝부분이 오류 사유로 남습니다 |
| 사용자가 중지한 뒤의 종료 | 코드와 상관없이 중지됨 |
화면에서 중지 를 누르면 플랫폼이 Job 을 지우고, 파드는 SIGTERM 을 받습니다. 유예 시간(서버 설정 GEO_MLOPS_TRAINING_GRACE_PERIOD, 기본 60초) 안에 끝나지 않으면 SIGKILL 입니다. 권장 처리:
import signal
stop_requested = False
def on_sigterm(*_):
global stop_requested
stop_requested = True # 핸들러 안에서 죽지 말고 표시만 한다
signal.signal(signal.SIGTERM, on_sigterm)
for epoch in range(EPOCHS):
...
save_checkpoint()
if stop_requested: # 안전한 지점(epoch 경계)에서 확인
break
체크포인트는 MLflow 아티팩트로 올려야 의미가 있습니다. /geo/work 는 파드와 함께 사라집니다. SIGKILL 로 끝나도 MLflow run 은 플랫폼이 KILLED 로 닫습니다.
MLflow 사용 규칙 두 가지
주소·토큰·실험·run 이 모두 환경 변수로 이미 설정돼 있습니다. mlflow.autolog() 한 줄이나 mlflow.log_metrics(...) 만으로 연결됩니다.
run 은 하나만 씁니다. 두 번째 start_run() 부터는 별도 run 이 생겨 화면과 자동 등록 대상에서 빠집니다. fold 별 학습처럼 결과가 여럿이면 지표 이름으로 구분해 한 run 에 기록하세요(fold0/loss, fold1/loss).