실험 추적 (Experiment Tracking) (v1.2+)
GenD 는 MLflow 트래킹 서버를 스토어로 사용하는 wandb 스타일 실험 추적 UI 를
제공한다. 별도 SDK 없이 표준 mlflow SDK 로 기록하면 ML 허브 → 실험
에서 런 비교 차트·라이브 갱신·시스템 메트릭을 볼 수 있다.
설계 결정은 ADR-0037 참조.
시작하기 전에 — 워크스페이스 배정 (전제 조건)
키 발급·태깅이 전부 워크스페이스에 묶이므로, 배정이 먼저다.
- 가입 직후에는 워크스페이스가 없다 — 화면 상단에 워크스페이스 없음 표시가 보인다면 이 상태다.
- 관리자에게 워크스페이스 배정(Keycloak
/tenants/<슬러그>그룹 Join)을 요청한다. 배정 후 다시 로그인하면 상단에 워크스페이스 이름이 뜬다. - 내 워크스페이스 slug 는 키 발급 패널(ML 허브 → 실험 → 외부 학습 서버
연결)의 키 목록에서
· <슬러그>접미사로 확인할 수 있다.
오류 없이 발급되지만 워크스페이스 태깅 없이 발급된다 — 실험이 워크스페이스
필터에 잡히지 않는다. 키 목록에서 · 슬러그 접미사가 없는 키가 그 상태다.
배정을 받은 뒤 재발급하면 된다.
빠른 시작 (코드스페이스)
코드스페이스 자격(Keycloak codespace-users 그룹)이
필요하다. 코드스페이스에는 트래킹 서버와 정합 버전의 mlflow 가 사전
설치되어 있고(현재 3.15.1) 트래킹 URI 가 클러스터 내부 MLflow 를 가리킨다.
학습 코드에서:
import mlflow
mlflow.set_experiment("my-experiment")
# 워크스페이스 소속 태깅 — API 의 ?workspace= 필터가 이 태그를 사용한다 (UI 필터는 후속 예정)
mlflow.set_experiment_tag("gend.workspace", "<워크스페이스 slug>")
with mlflow.start_run():
mlflow.log_param("lr", 0.01)
for epoch in range(10):
mlflow.log_metric("loss", loss_value, step=epoch)
시스템 메트릭(CPU/메모리)은 코드 없이 자동 수집된다 (v1.2+, #3053) —
코드스페이스 이미지가 MLFLOW_ENABLE_SYSTEM_METRICS_LOGGING=true 를 기본으로
켜둔다. 실험 화면의 시스템 탭에서 볼 수 있다.
외부 학습 서버에서 (v1.2+)
사내 클러스터 밖 GPU 서버에서도 표준 mlflow SDK 로 로깅할 수 있다. 학습 코드는 코드스페이스와 똑같고, 자격증명만 준비하면 된다.
자격증명은 누가 쓰느냐에 따라 두 트랙이다.
| 트랙 | 언제 | 발급 | 수명 |
|---|---|---|---|
| 개인 트래킹 키 | 개인 학습 서버 — 대부분 여기 | 본인이 직접 (관리자 불필요) | 무기한 (폐기 시까지) |
| M2M 서비스 클라이언트 | 팀 공용 서버 · CI 파이프라인 | 관리자 | 무기한 |
개인 트래킹 키 — 셀프서비스 (v1.2+)
관리자 권한이 필요 없다. 아래 화면은 전부 analyst 역할만 가진 일반 계정으로
찍은 것이다 — 같은 계정으로 관리자 API(/api/v1/admin/service-clients)를 부르면
403 이 나오지만, 본인 키 발급(/api/v1/me/tracking-credentials)은 200 이다.
1. ML 허브 → 실험
왼쪽 사이드바 ML 허브 아래 실험 으로 들어간다.

2. 우상단 "외부 학습 서버 연결"

누르면 오른쪽에서 패널이 열린다.

3. 용도 메모를 적고 발급
나중에 어느 서버의 키인지 알아볼 수 있게 적는다 (예: 연구실 GPU 서버).

4. Client ID / Secret 확인 — secret 은 이때 한 번만 보인다

- Secret 은 기본적으로 가려져 있다. 표시(눈 모양)를 눌러야 보이고, 설정 스니펫의 secret 자리도 함께 드러난다.
- 창을 닫으면 다시 볼 수 없다. 잃어버리면 같은 화면에서 회전(rotate)해야 한다.
- 설정 복사 를 누르면 아래 내용이 값이 채워진 채로 클립보드에 들어간다.
pip install "gend-cli[mlflow] @ git+https://github.com/genonai/DataX.git@main#subdirectory=apps/cli"
export MLFLOW_TRACKING_URI='https://gend.genon.ai/api/v1/mlflow-sdk'
export MLFLOW_TRACKING_AUTH=gend
export GEND_CLIENT_ID='gend-user-<본인>-<4자리>-<6자리>'
export GEND_CLIENT_SECRET='<발급 시 표시된 secret>'
export MLFLOW_ENABLE_SYSTEM_METRICS_LOGGING=true
pip install mlflow 만으로는 안 된다MLFLOW_TRACKING_AUTH=gend 는 gend 라는 이름의 request-auth-provider 플러그인을
찾는다. 이 플러그인은 gend-cli 패키지가 entry-point 로 등록하므로, 표준 mlflow 만
설치하면 MLflowException: Unrecognized request auth provider 로 실패한다.
설치 후 다음이 ['gend'] 를 출력해야 한다:
python - <<'EOF'
from importlib.metadata import entry_points
names = sorted({e.name for e in entry_points(group="mlflow.request_auth_provider")})
print(names)
if "gend" not in names:
raise SystemExit(f"gend 플러그인이 없다 — gend-cli 설치를 확인할 것. 등록된 것: {names}")
EOF
gend 가 없으면 0 이 아닌 종료 코드로 실패한다. 다른 provider 가 함께
설치돼 있어 목록이 ['gend'] 하나가 아니어도, gend 만 들어 있으면 정상이다.
[mlflow] extra 를 그대로 쓰는 것을 권장한다. extra 는
mlflow>=3.11,<4.0 과 psutil(시스템 메트릭 필수)·nvidia-ml-py(GPU) 를
함께 설치한다 — 트래킹 서버(3.15.1)와 호환이고, 2.x 가 깔린 서버라면 3.x 로
올라간다(#3122 보안 하한).
# 권장 — mlflow 3.x + psutil + nvidia-ml-py 함께 설치
pip install "gend-cli[mlflow] @ git+https://github.com/genonai/DataX.git@main#subdirectory=apps/cli"
# extra 없이 설치하는 경우(이미 맞는 mlflow 를 관리 중일 때) —
# ★ 설정 스니펫이 시스템 메트릭을 켜므로 psutil 을 반드시 함께 설치할 것.
# 없으면 start_run() 이 경고가 아니라 예외로 죽는다.
pip install "gend-cli @ git+https://github.com/genonai/DataX.git@main#subdirectory=apps/cli" psutil
사내망에서 git 설치가 막히면 리포 사본에서 pip install './apps/cli[mlflow]'
(mlflow 3.x 환경이면 pip install './apps/cli').
값이 작은따옴표로 감싸인 건 의도된 것이다 — secret 에 $, ;, < 같은 셸
특수문자가 있어도 그대로 전달된다.
키는 발급 시점의 활성 워크스페이스에 바인딩된다. 이 키로 만든 실험은 해당
워크스페이스로 자동 태깅된다(실측: 실험에 gend.workspace = crm-demo 기록).
사용자당 활성 키는 기본 5개까지이며(GEND_PERSONAL_TRACKING_KEY_QUOTA), 같은
화면에서 회전·폐기할 수 있다.
어느 워크스페이스에도 속하지 않은 계정이 키를 발급하면 오류 없이 201 로
성공하지만 워크스페이스 태그가 붙지 않는다. 나중에 실험 목록의 워크스페이스
필터에 잡히지 않는데 화면에는 그 사실이 드러나지 않는다.
발급 응답의 workspace_slug 가 비어 있으면 이 경우다 — 관리자에게 멤버십을
요청한 뒤 다시 발급할 것.
폐기하면 새 토큰 발급이 즉시 차단된다. 그러나 서버는 요청마다 키의 폐기 여부를 조회하지 않고 JWT 서명·만료만 검증하며, 클라이언트도 발급받은 액세스 토큰을 만료 전까지 캐시한다. 따라서 폐기 시점에 이미 돌고 있는 프로세스는 캐시된 토큰이 만료될 때까지(realm 액세스 토큰 수명, 기본 5분) 계속 로깅에 성공한다.
키가 유출됐다면 폐기만으로 부족하다 — 학습 프로세스를 직접 중단시켜야 한다.
설계 배경은 ADR-0043 참조.
학습 코드 — 복사해서 바로 돌아가는 예제
위 스니펫의 환경변수를 설정한 뒤 아래를 그대로 실행하면 된다. 이 문서의 모든 결과 화면은 이 스크립트를 실제로 돌려서 찍은 것이다.
import json
import os
import pathlib
import matplotlib
import numpy as np
matplotlib.use("Agg") # 헤드리스 서버 — 디스플레이 없이 그림 저장
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, f1_score, log_loss, roc_auc_score
from sklearn.model_selection import train_test_split
import mlflow
mlflow.set_experiment("guide-demo")
# 코드스페이스에서 돌린다면 아래 한 줄 추가 — 키 자동 태깅이 없는 경로다
# mlflow.set_experiment_tag("gend.workspace", "<워크스페이스 slug>")
# ① autolog — 이걸 켜야 **직접 적지 않은 디폴트 하이퍼파라미터까지** 기록된다.
# 끄면 log_params 로 넘긴 것만 남는다.
mlflow.sklearn.autolog(log_models=False, silent=True)
# 실행마다 바꿀 값은 여기 한 곳에만 둔다 — 곡선용 모델과 최종 모델이 갈리지 않게.
RUN_NAME = os.environ.get("RUN_NAME", "rf-baseline")
HPARAMS = {
"n_estimators": int(os.environ.get("N_ESTIMATORS", 120)),
"max_depth": int(os.environ.get("MAX_DEPTH", 12)),
"random_state": 42,
}
X, y = make_classification(n_samples=4000, n_features=16, n_informative=8, random_state=42)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25, random_state=42)
with mlflow.start_run(run_name=RUN_NAME) as run:
print("run_id:", run.info.run_id)
# ② 내가 정한 값 — 표에서 run 끼리 비교할 축이 된다
mlflow.log_params({"dataset": "synthetic-4k", "n_features": X.shape[1]})
model = RandomForestClassifier(**HPARAMS)
model.fit(X_tr, y_tr)
# ③ 학습 곡선 — **step 을 올려야 선이 그려진다.**
history = []
for step in range(20):
n = int(len(X_tr) * (0.05 + 0.95 * (step + 1) / 20))
m = RandomForestClassifier(**HPARAMS)
m.fit(X_tr[:n], y_tr[:n])
proba = m.predict_proba(X_te)
pred = np.argmax(proba, axis=1)
row = {
"val_accuracy": accuracy_score(y_te, pred),
"val_f1": f1_score(y_te, pred),
"val_auc": roc_auc_score(y_te, proba[:, 1]),
"val_logloss": log_loss(y_te, proba),
}
mlflow.log_metrics(row, step=step) # ← step 필수
history.append({"step": step, "n_train": n, **row})
# ④ 아티팩트 — 그림·표·설정 파일을 run 에 첨부한다
out = pathlib.Path("artifacts")
out.mkdir(exist_ok=True)
fig, ax = plt.subplots(figsize=(6, 4))
ax.plot([h["step"] for h in history], [h["val_auc"] for h in history], marker="o")
ax.set_xlabel("step"); ax.set_ylabel("val_auc"); ax.set_title("Validation AUC")
fig.tight_layout(); fig.savefig(out / "val_auc.png", dpi=120); plt.close(fig)
(out / "history.json").write_text(json.dumps(history, indent=2))
(out / "feature_importance.csv").write_text(
"feature,importance\n"
+ "\n".join(f"f{i},{v:.6f}" for i, v in enumerate(model.feature_importances_))
)
mlflow.log_artifacts(str(out), artifact_path="plots")
# ⑤ 최종 지표 — 단일값. 표의 정렬·비교에 쓴다
final_proba = model.predict_proba(X_te)
mlflow.log_metrics({
"final_accuracy": accuracy_score(y_te, model.predict(X_te)),
"final_auc": roc_auc_score(y_te, final_proba[:, 1]),
})
두 번째 run 은 같은 파일을 환경변수만 바꿔 돌린다 — 이 문서의 비교 화면이 그렇게 만들어졌다.
python train.py # rf-baseline (max_depth=12)
RUN_NAME=rf-deep MAX_DEPTH=24 N_ESTIMATORS=200 python train.py # rf-deep
step= 이 필요하다mlflow.sklearn.autolog() 가 남기는 training_* 메트릭은 fit() 마다 기록되지만
step 이 올라가지 않는다. 차트는 step 을 x축으로 그리므로 이런 메트릭은 값이
여러 개여도 점 하나로 뭉친다. 학습 곡선을 보려면 위 ③처럼
mlflow.log_metrics(..., step=i) 로 직접 기록해야 한다.
결과가 UI 에 어떻게 나오는가
위 스크립트를 두 번(rf-baseline, max_depth 만 바꾼 rf-deep) 돌린 실제 화면이다.
실험 목록

실행(run) 테이블 — 파라미터·지표가 열로 뜬다

bootstrap·ccp_alpha·class_weight·criterion 은 직접 적지 않았는데도 열로
뜬다 — autolog() 가 넣어준 것이다. dataset 은 ②에서 직접 넣은 값,
final_accuracy 는 ⑤의 지표다. max_depth 가 12 / 24 로 갈리는 것도 바로 보인다.
실행 상세 — 파라미터 · 지표 · 아티팩트
왼쪽 실행 ID(76ac18cd 같은 8자리)를 누르면 오른쪽에서 상세 시트가 열린다.
행의 다른 셀(이름·상태·지표)은 클릭해도 열리지 않는다.
파라미터 목록에서 dataset·n_features 는 스크립트가 log_params() 로 직접
넣은 값이고, 나머지(bootstrap, criterion, max_features …)는 autolog() 가
모델 생성자 파라미터를 통째로 기록한 것이다.
autolog() 는 생성자 파라미터 전량을 기록하되 값은 실제로 쓰인 것을 넣는다.
그래서 이 목록의 max_depth=12·n_estimators=120·random_state=42 는
sklearn 디폴트(None/100/None)가 아니라 위 스크립트가 넘긴 값이다.
"내가 안 적은 것"과 "sklearn 디폴트"는 다르다.

아래로 내리면 지표와 아티팩트가 있다.

- 파라미터: autolog 디폴트 20여 개 + 내가 넣은 값
- 지표:
val_*(20스텝) ·final_*(단일값) ·training_*(autolog) - 아티팩트: ④에서 올린
plots/디렉토리와 autolog 가 만든training_confusion_matrix.png·training_roc_curve.png등
mlflow.user 태그는 학습 서버의 OS 계정명이지 인증된 신원이 아니다. 공용
서버에서는 모든 run 이 같은 이름으로 뭉치고, 환경변수로 임의 값을 넣을 수도 있다.
신뢰 가능한 귀속으로 바꾸는 작업은 #3046
에서 다룬다.
실행 비교 — 학습 곡선
run 을 2개 이상 선택하고 비교 를 누르면 곡선이 겹쳐 그려진다.

기본 선택은 실제로 곡선이 그려지는 메트릭(서로 다른 step 이 2개 이상)이 우선
채워진다. training_* 처럼 step 이 하나뿐인 메트릭은 체크하면 점 하나로 보인다.
M2M 서비스 클라이언트 — 팀 공용·CI
여러 사람이 공유하는 학습 서버나 CI 파이프라인은 개인 키 대신 M2M 클라이언트를
쓴다. 관리자가 관리 콘솔 → 서비스 클라이언트 에서 발급하며, 이때
워크스페이스 필드를 채워야 실험 자동 태깅이 동작한다(비우면 태깅 없음).
환경변수는 개인 키와 동일하다 — GEND_CLIENT_ID/GEND_CLIENT_SECRET 에 발급받은
값을 넣는다.
인증 플러그인 동작 (v1.2+)
설치 확인 방법은 위 경고 상자를 참조한다.
이 플러그인은 요청마다 토큰 만료를 확인해 필요할 때 재발급하므로 며칠짜리 학습도 중간에 끊기지 않는다. 자격증명이 없으면 조용히 익명으로 진행하지 않고 즉시 예외를 던진다(fail-closed).
MLFLOW_TRACKING_TOKEN 을 함께 설정하면 그 값이 우선해 자동 갱신이 무력화된다.
둘을 같이 쓰지 않는다. (수동 토큰 방식은 몇 분짜리 임시 실험에만 적합하다.)
GEND_CLIENT_SECRET 은 학습 서버의 환경변수·쉘 히스토리에 남는다 — 파일 권한을
제한하거나 시크릿 매니저를 사용할 것.
막혔을 때
| 증상 | 원인 | 대처 |
|---|---|---|
401 Unauthorized | 키 오타, 폐기된 키, MLFLOW_TRACKING_AUTH=gend 누락 | 값 확인 후 재설정. 키를 폐기했다면 새로 발급 |
RuntimeError (SDK 시작 시) | GEND_CLIENT_ID/SECRET 미설정 — fail-closed | 두 값을 모두 설정 |
403 Forbidden (키 발급 시) | 본인이 멤버가 아닌 워크스페이스 지정 | 활성 워크스페이스로 발급하거나 관리자에게 멤버십 요청 |
409 Conflict (키 발급 시) | 개인 키 상한 도달 | 안 쓰는 키를 폐기한 뒤 재시도 |
413 Payload Too Large | 단일 artifact 가 상한(기본 2GB) 초과 | 파일을 쪼개거나 관리자에게 GEND_MLFLOW_ARTIFACT_MAX_MB 상향 요청 |
404 — register_model / delete_* | 의도된 차단 (아래 참조) | 모델 등록은 GenD UI 의 모델 스테이지에서 |
| artifact 관련 호출이 전부 실패 | 전환 이전에 만든 옛 실험 | 새 실험을 만들어 사용 (아래 참조) |
| 실험이 목록에 안 보임 | 태그 칩 필터가 켜져 있거나, 무태깅 키로 만든 실험이 (미분류) 에 있음 | 필터 칩 해제 → (미분류) 섹션 확인 → 키 목록의 · 슬러그 접미사 확인(없으면 재발급 — 위 "시작하기 전에") |
| 시스템 메트릭 탭이 비어 있음 | MLFLOW_ENABLE_SYSTEM_METRICS_LOGGING 미설정 또는 psutil 부재 | 설정 스니펫 재적용. GPU 행은 nvidia-ml-py 필요(없으면 경고만, CPU/메모리는 수집됨) |
지원되지 않는 호출
프록시는 로깅에 필요한 경로만 허용한다(allowlist fail-closed). 아래는 의도적으로 차단되며 404 가 반환된다:
mlflow.register_model— 모델 레지스트리 쓰기delete_tag/delete_run/delete_experiment등 삭제 계열
log_model 은 registered_model_name 없이 호출하면 정상 동작한다(artifact 로
저장). 이름을 지정하면 artifact 는 올라가지만 레지스트리 등록 단계에서 예외가 난다.
artifact 제약
artifact(체크포인트·모델)도 프록시를 통해 업/다운로드되므로 학습 서버에 S3
자격증명이 필요 없다 — 단, 이 기능 도입 이후 생성된 신규 실험에 한한다.
단일 artifact 기본 상한은 2GB, 초과 시 413 이며 운영자가
GEND_MLFLOW_ARTIFACT_MAX_MB(환경 변수)
로 조정할 수 있다.
도입 이전에 생성된 실험은 artifact_location 이 여전히 s3:// 라 SDK 가
프록시를 우회해 직접 S3 접근을 시도한다 — 외부 서버는 내부 전용
스토리지(SeaweedFS/Ceph RGW)에 닿지 않으므로 목록 조회를 포함해 artifact 관련
호출이 전부 실패한다. list_artifacts 도 서버측 REST 를 타지 않고 클라이언트가
run.info.artifact_uri 로 리포지토리를 직접 구성해 호출하므로 예외가 아니다.
메트릭·파라미터 로깅과 GenD UI 조회는 무관하게 정상이다.
시스템 메트릭 (CPU/메모리/GPU)
기본으로 켜져 있다 (v1.2+, #3053). 학습 코드에서 아무것도 하지 않아도
system/ 접두사 메트릭이 수집되어 비교 화면의 시스템 탭에 표시된다.
wandb 와 같은 동작이다.
| 경로 | 어떻게 켜지나 |
|---|---|
| 코드스페이스 | 이미지가 MLFLOW_ENABLE_SYSTEM_METRICS_LOGGING=true 를 기본 설정 |
| 외부 학습 서버 | 발급 화면의 설정 복사 스니펫에 같은 환경변수가 포함됨 |
수집되는 항목 (prod 실측 8종):
system/cpu_utilization_percentage system/system_memory_usage_megabytes
system/system_memory_usage_percentage system/disk_usage_megabytes
system/disk_usage_percentage system/disk_available_megabytes
system/network_receive_megabytes system/network_transmit_megabytes
psutil 이 없으면 학습이 죽는다이 변수가 켜진 상태에서 psutil 이 없으면 MLflow 는 경고가 아니라 예외를
던져 start_run() 자체가 실패한다:
MlflowException: Failed to start system metrics monitoring as package `psutil` is not installed.
gend-cli[mlflow] 가 psutil 을 함께 설치하므로 문서대로 설치했다면 문제없다.
직접 mlflow 만 설치한 환경이라면 pip install psutil 을 함께 해야 한다.
끄려면 export MLFLOW_ENABLE_SYSTEM_METRICS_LOGGING=false.
GPU 메트릭은 pynvml 이 있어야 한다. gend-cli[mlflow] 에 포함되어 있으므로
NVIDIA GPU 학습 서버에서는 자동으로 함께 수집된다. GPU 가 없거나 pynvml 이
없으면 MLflow 가 경고만 남기고 넘어간다(psutil 과 달리 크래시하지 않는다).
코드스페이스는 CPU 전용이라 pynvml 을 넣지 않았다.
런 비교
- ML 허브 → 실험 에서 실험 클릭 → run 테이블(실행 ID·이름·상태·하이퍼파라미터·메트릭).
- 체크박스로 run 을 선택(최대 6개)하고 비교 버튼. 1개만 선택해도 학습 곡선을 볼 수 있다. run 행의 실행 ID 를 클릭하면 열리는 상세 시트에서 학습 곡선 보기 로 바로 갈 수도 있다.
- 비교 화면: 메트릭별 학습 곡선 오버레이(x축 Step/시간 토글, 스무딩 슬라이더), 파라미터 diff 테이블("차이만 보기" — 값이 다른 행이 강조된다), 시스템 메트릭 탭.
범례에는 각 run 의 상태가 함께 표시된다. 중단(KILLED)됐거나 메트릭이 하나도
없는 run 은 데이터 없음 으로 표시되어, 곡선이 안 그려지는 이유를 화면에서
바로 알 수 있다.
학습이 진행 중(RUNNING)인 run 이 화면에 있으면 8초 간격으로 자동 갱신되고, 전부 종료되면 자동으로 중단된다.
실험 그룹핑 — 프로젝트 태그 (v1.2+, #3233)
부서(워크스페이스) 하나에서 여러 프로젝트(로봇 학습, LLM 학습 …)를 돌릴 때
실험을 묶는 축은 gend.project 태그다. wandb 를 써 봤다면 개념이 그대로
대응된다:
| wandb | GenD | 역할 |
|---|---|---|
| entity (team) | 워크스페이스 | 접근 경계 — 멤버·권한·격리 |
| project | 실험 (experiment) | 작업 단위 |
| group | run 태그 / parent run | run 을 안에서 묶기 |
| run | run | 학습 1회 |
"로봇 자세 추정" 프로젝트 = 실험 하나(robot-pose)로 만들고, 시도들을
run 으로 쌓는다. robot-pose-v1, robot-pose-v2 처럼 실험을 잘게 만들면
wandb 로 치면 프로젝트를 버전마다 새로 파는 것이다 — v1/v2 구분은 run
이름·태그·파라미터로 한다.
실험이 여러 개로 갈리는 큰 프로젝트라면 gend.project 태그로 묶는다:
mlflow.set_experiment("robot-pose")
mlflow.set_experiment_tag("gend.project", "robot") # 프로젝트 묶음

UI: ML 허브 → 실험 목록에 태그가 칩으로 표시된다 (mlflow.* 시스템
태그는 제외). 칩을 클릭하면 그 태그를 가진 실험만 남고, 다시 클릭하면
해제된다 — 이름 검색과 AND 로 조합된다. gend.project 태그를 가진 실험이
하나라도 있으면 목록이 프로젝트별 섹션으로 묶이고, 태그 없는 실험은
(미분류) 섹션에 모인다(아무도 태그를 쓰지 않으면 기존과 같은 평평한
목록이다).
SDK: 서버측 필터도 지원된다 — 태그 키는 자유이므로 임의 축으로 조회할 수 있다:
mlflow.search_experiments(filter_string="tags.`gend.project` = 'robot'")
태그 기반 필터·그룹핑은 태그를 붙인 실험만 잡는다. 태그 없는 실험은 필터를 켜면 결과에서 빠지고, 그룹 화면에서는 (미분류) 로 모인다 — 조용히 사라지는 것을 막기 위한 표시이니, (미분류) 가 늘고 있다면 태깅 관례가 새고 있다는 신호다.
네이티브 MLflow UI (v1.2+, #3279)
GenD 화면(ML 허브)보다 강한 축 — run 을 태그·파라미터로 Group by, 차트 빌더, 실험 간 비교 — 이 필요하면 MLflow 자체 UI 를 쓴다:

- 누가: Keycloak SSO 계정이면 누구나. 처음 접속하면 Sign In 화면(403) 이 뜬다 — 버튼을 눌러 Keycloak 로그인하면 된다(이미 GenD 나 Dagster 에 로그인한 브라우저는 바로 통과한다. 세션을 공유한다).
- GenD 목록의 태그 칩·
gend.project그룹핑과 같은 데이터를 본다 — 태그는 어느 쪽에서 붙여도 양쪽에 보인다. - 막혔을 때: 로그인 후에도 403 이면 관리자에게 — 게이트웨이 인증 연동
문제다(#3279 참조, 운영:
admin-ops게이트웨이 절).
제한
- run 비교는 한 번에 최대 6개.
- artifact 는 목록 조회와 SDK 다운로드를 지원한다(UI 인라인 미리보기는 후속).
- workspace 필터는 현재 API 전용이다 (
GET /api/v1/models/experiments?workspace=<slug>).gend.workspace태그가 일치하는 실험만 반환하지만, ML Hub UI 에는 아직 이 필터를 조작하는 컨트롤이 없다(후속 예정). 태그 없는 레거시 실험은 (필터를 걸지 않는 한) UI 목록에서 계속 보인다.