- Published on
Day 41: MLflow, Experiment Tracking Và Model Registry
- Authors

- Name
- Trần Mạnh Thắng
- @TranManhThang96
1. Mục tiêu bài học
Mục tiêu của Day 41 không phải là mở MLflow UI cho đẹp. Mục tiêu là tạo được một audit trail đủ tin cậy để khi model được deploy, team có thể trả lời:
Model này được train bằng data version nào?
Code commit nào tạo ra model này?
Config và hyperparameters chính xác là gì?
Metric validation/test có đạt release gate không?
Artifact đánh giá nằm ở đâu?
Ai approve model version này?
Nếu production lỗi, rollback về version nào?
Sau bài này, bạn cần làm được:
- Chạy MLflow Tracking Server local hoặc self-hosted.
- Log
params,metrics,artifacts, model, dataset metadata và code version trong cùng một run. - Register model version vào Model Registry.
- Dùng alias như
candidate,champion,shadowđể quản lý deployment target. - Viết decision note trả lời production readiness, trade-off, rollback và limitation.
Ghi chú API hiện tại: từ MLflow 2.9.0, Model Registry stages như Staging/Production đã bị deprecate. Workflow mới nên dùng model version tags và aliases, ví dụ models:/sentiment-classifier@champion.
2. Mental model
Experiment tracking là metadata system cho quá trình phát triển model.
training code
-> run metadata
-> params
-> metrics
-> dataset lineage
-> artifacts
-> logged model
-> registered model version
-> alias candidate/champion
-> deployment reads exact model URI
Nếu chỉ lưu model.pkl, bạn mất gần hết bối cảnh ra quyết định. Nếu chỉ log metric, bạn vẫn không biết metric đó đến từ dataset nào, code nào, split nào, seed nào và evaluation script nào.
Một run có giá trị khi nó đủ để phục vụ ba việc:
| Việc cần làm | Câu hỏi cần trả lời | Metadata cần có |
|---|---|---|
| Reproduce | Có chạy lại ra kết quả tương đương không? | code commit, dataset version/hash, config, seed, package versions |
| Compare | Run nào tốt hơn trong cùng điều kiện? | metric chính, metric phụ, eval set, latency, cost |
| Deploy/Rollback | Version nào đang chạy và quay về đâu khi lỗi? | registered model version, alias, model card, approval, rollback target |
3. Experiment tracking cần log gì?
Tối thiểu mỗi run nên log:
| Nhóm | Ví dụ | Lý do |
|---|---|---|
| Params | model_type, learning_rate, max_features, epochs, batch_size, seed | So sánh cấu hình và reproduce |
| Metrics | accuracy, macro_f1, eval_loss, p95_latency_ms, cost_per_1k_predictions | Chọn best run theo business gate |
| Artifacts | classification_report.json, confusion_matrix.png, model_card.md, eval_summary.json | Review bằng người, audit và debug |
| Dataset | dataset_version, dataset_hash, train/validation/test split | Tránh so sánh lệch dữ liệu |
| Code | git_commit, training_script, package lock | Biết code nào sinh model |
| Tags | owner, task, environment, approval_status | Tìm kiếm và governance |
Không nên log raw PII vào params, tags, artifact hoặc sample predictions. Nếu cần sample, phải redact hoặc hash trước.
4. Kiến trúc local và production
Local learning setup:
train_day41.py
-> MLflow Tracking Server
-> SQLite backend store
-> local artifact directory
-> Model Registry
Production-style setup:
training job / CI pipeline
-> MLflow Tracking Server behind auth/TLS
-> Postgres/MySQL backend store
-> S3/GCS/Azure Blob artifact store
-> Model Registry
-> model validation job
-> deployment system loads models:/<name>@champion
-> monitoring writes drift/latency/error metrics
Local SQLite đủ cho học tập. Production không nên phụ thuộc vào SQLite và local filesystem vì thiếu backup, concurrency, access control và durability.
5. Step by step: chạy MLflow
Bước 1: Tạo môi trường
python3 -m venv .venv
source .venv/bin/activate
pip install -U mlflow scikit-learn pandas matplotlib
Nếu bạn train model từ Day 27 với Transformers/LoRA, cài thêm package tương ứng:
pip install -U torch transformers datasets peft accelerate
Bước 2: Chạy tracking server local
mkdir -p mlruns mlartifacts
mlflow server \
--host 127.0.0.1 \
--port 5000 \
--backend-store-uri sqlite:///mlruns/mlflow.db \
--default-artifact-root ./mlartifacts
Mở UI tại:
http://127.0.0.1:5000
Bước 3: Chuẩn bị dataset
Ví dụ tối thiểu cho Day 16 sentiment classifier:
text,label
"Sản phẩm dùng ổn, giao hàng nhanh",positive
"Ứng dụng lỗi liên tục sau khi cập nhật",negative
"Dịch vụ bình thường, chưa có gì nổi bật",neutral
Production thật cần dataset version rõ hơn: DVC, LakeFS, Delta table version, data warehouse snapshot hoặc ít nhất là file hash cộng với source URI.
6. Code ví dụ gần production
File gợi ý: train_day41.py.
Code dưới đây cố tình dùng TfidfVectorizer + LogisticRegression để tập trung vào MLOps workflow. Với Day 27 LoRA, giữ cùng cấu trúc tracking nhưng thay phần training/model flavor.
from __future__ import annotations
import argparse
import hashlib
import json
import os
import subprocess
import sys
import time
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Any
import mlflow
import mlflow.data
import mlflow.sklearn
import pandas as pd
from mlflow import MlflowClient
from mlflow.models import infer_signature
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report, f1_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
@dataclass(frozen=True)
class TrainConfig:
tracking_uri: str
experiment_name: str
registered_model_name: str
dataset_path: Path
dataset_version: str
artifact_dir: Path
test_size: float = 0.2
random_state: int = 42
max_features: int = 30000
ngram_min: int = 1
ngram_max: int = 2
classifier_c: float = 1.0
min_macro_f1: float = 0.78
max_p95_latency_ms: float = 30.0
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as f:
for chunk in iter(lambda: f.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def git_commit() -> str:
try:
return subprocess.check_output(
["git", "rev-parse", "HEAD"],
stderr=subprocess.DEVNULL,
text=True,
).strip()
except Exception:
return "unknown"
def pip_freeze() -> str:
try:
return subprocess.check_output(
[sys.executable, "-m", "pip", "freeze"],
stderr=subprocess.DEVNULL,
text=True,
)
except Exception:
return "requirements unavailable\n"
def write_json(path: Path, payload: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
def load_dataset(path: Path) -> pd.DataFrame:
df = pd.read_csv(path)
required_columns = {"text", "label"}
missing = required_columns - set(df.columns)
if missing:
raise ValueError(f"Dataset thiếu cột bắt buộc: {sorted(missing)}")
df = df.dropna(subset=["text", "label"]).copy()
df["text"] = df["text"].astype(str).str.strip()
df["label"] = df["label"].astype(str).str.strip()
df = df[(df["text"] != "") & (df["label"] != "")]
if len(df) < 20:
raise ValueError("Dataset quá nhỏ để đánh giá ổn định. Cần ít nhất 20 dòng.")
return df
def build_model(cfg: TrainConfig) -> Pipeline:
return Pipeline(
steps=[
(
"tfidf",
TfidfVectorizer(
lowercase=True,
max_features=cfg.max_features,
ngram_range=(cfg.ngram_min, cfg.ngram_max),
),
),
(
"classifier",
LogisticRegression(
C=cfg.classifier_c,
max_iter=1000,
class_weight="balanced",
random_state=cfg.random_state,
),
),
]
)
def estimate_latency_ms(model: Pipeline, samples: pd.Series, repeats: int = 30) -> dict[str, float]:
durations = []
batch = samples.head(min(len(samples), 32))
for _ in range(repeats):
start = time.perf_counter()
model.predict(batch)
durations.append((time.perf_counter() - start) * 1000)
durations = sorted(durations)
p95_index = max(0, int(len(durations) * 0.95) - 1)
return {
"mean_latency_ms": sum(durations) / len(durations),
"p95_latency_ms": durations[p95_index],
}
def train_and_log(cfg: TrainConfig) -> str:
mlflow.set_tracking_uri(cfg.tracking_uri)
mlflow.set_experiment(cfg.experiment_name)
dataset_hash = sha256_file(cfg.dataset_path)
commit = git_commit()
df = load_dataset(cfg.dataset_path)
stratify = df["label"] if df["label"].value_counts().min() >= 2 else None
train_df, test_df = train_test_split(
df,
test_size=cfg.test_size,
random_state=cfg.random_state,
stratify=stratify,
)
model = build_model(cfg)
run_name = f"{cfg.registered_model_name}-{cfg.dataset_version}-{commit[:8]}"
with mlflow.start_run(run_name=run_name) as run:
run_id = run.info.run_id
mlflow.log_params(
{
"model_type": "tfidf_logistic_regression",
"dataset_path": str(cfg.dataset_path),
"dataset_version": cfg.dataset_version,
"dataset_hash": dataset_hash,
"test_size": cfg.test_size,
"random_state": cfg.random_state,
"max_features": cfg.max_features,
"ngram_range": f"{cfg.ngram_min},{cfg.ngram_max}",
"classifier_c": cfg.classifier_c,
}
)
mlflow.set_tags(
{
"task": "vietnamese_sentiment",
"owner": os.getenv("USER", "unknown"),
"git_commit": commit,
"training_script": "train_day41.py",
"approval_status": "pending",
}
)
train_input = mlflow.data.from_pandas(
train_df,
source=str(cfg.dataset_path),
targets="label",
name=f"{cfg.dataset_version}-train",
)
test_input = mlflow.data.from_pandas(
test_df,
source=str(cfg.dataset_path),
targets="label",
name=f"{cfg.dataset_version}-test",
)
mlflow.log_input(train_input, context="training")
mlflow.log_input(test_input, context="evaluation")
model.fit(train_df["text"], train_df["label"])
predictions = model.predict(test_df["text"])
report = classification_report(
test_df["label"],
predictions,
output_dict=True,
zero_division=0,
)
metrics = {
"accuracy": accuracy_score(test_df["label"], predictions),
"macro_f1": f1_score(test_df["label"], predictions, average="macro"),
"weighted_f1": f1_score(test_df["label"], predictions, average="weighted"),
}
metrics.update(estimate_latency_ms(model, test_df["text"]))
mlflow.log_metrics(metrics)
cfg.artifact_dir.mkdir(parents=True, exist_ok=True)
write_json(cfg.artifact_dir / "classification_report.json", report)
write_json(
cfg.artifact_dir / "eval_summary.json",
{
"run_id": run_id,
"registered_model_name": cfg.registered_model_name,
"metrics": metrics,
"dataset_version": cfg.dataset_version,
"dataset_hash": dataset_hash,
"git_commit": commit,
},
)
(cfg.artifact_dir / "model_card.md").write_text(
"\n".join(
[
f"# Model Card: {cfg.registered_model_name}",
"",
"## Intended use",
"Vietnamese sentiment classification for controlled text inputs.",
"",
"## Validation",
f"- Dataset version: `{cfg.dataset_version}`",
f"- Dataset hash: `{dataset_hash}`",
f"- Macro F1: `{metrics['macro_f1']:.4f}`",
f"- P95 latency ms: `{metrics['p95_latency_ms']:.2f}`",
"",
"## Known limitations",
"- Chưa kiểm thử drift theo thời gian.",
"- Chưa kiểm thử fairness theo domain/user segment.",
"- Không log raw PII; sample predictions phải được redact trước khi chia sẻ.",
"",
"## Rollback",
"Deployment nên load `models:/sentiment-classifier@champion`; rollback bằng cách trỏ alias `champion` về version trước đó.",
]
),
encoding="utf-8",
)
(cfg.artifact_dir / "requirements-lock.txt").write_text(
pip_freeze(),
encoding="utf-8",
)
mlflow.log_artifacts(str(cfg.artifact_dir), artifact_path="reports")
signature = infer_signature(test_df["text"].head(5), model.predict(test_df["text"].head(5)))
model_info = mlflow.sklearn.log_model(
sk_model=model,
name="model",
signature=signature,
input_example=test_df["text"].head(2),
registered_model_name=cfg.registered_model_name,
)
client = MlflowClient()
version = str(model_info.registered_model_version)
client.set_model_version_tag(
name=cfg.registered_model_name,
version=version,
key="validation_status",
value="candidate",
)
client.set_model_version_tag(
name=cfg.registered_model_name,
version=version,
key="source_run_id",
value=run_id,
)
passed_gate = (
metrics["macro_f1"] >= cfg.min_macro_f1
and metrics["p95_latency_ms"] <= cfg.max_p95_latency_ms
)
client.set_model_version_tag(
name=cfg.registered_model_name,
version=version,
key="release_gate",
value="passed" if passed_gate else "failed",
)
if passed_gate:
client.set_registered_model_alias(
name=cfg.registered_model_name,
alias="candidate",
version=version,
)
return run_id
def parse_args() -> TrainConfig:
parser = argparse.ArgumentParser()
parser.add_argument("--tracking-uri", default="http://127.0.0.1:5000")
parser.add_argument("--experiment-name", default="day41-sentiment-classifier")
parser.add_argument("--registered-model-name", default="sentiment-classifier")
parser.add_argument("--dataset-path", type=Path, required=True)
parser.add_argument("--dataset-version", required=True)
parser.add_argument("--artifact-dir", type=Path, default=Path("artifacts/day41"))
parser.add_argument("--classifier-c", type=float, default=1.0)
args = parser.parse_args()
return TrainConfig(**vars(args))
if __name__ == "__main__":
config = parse_args()
print(json.dumps(asdict(config), default=str, ensure_ascii=False, indent=2))
print(f"Logged run_id={train_and_log(config)}")
Chạy một run:
python train_day41.py \
--dataset-path data/sentiment_v1.csv \
--dataset-version sentiment-v1 \
--classifier-c 1.0
Chạy nhiều run để so sánh:
python train_day41.py --dataset-path data/sentiment_v1.csv --dataset-version sentiment-v1 --classifier-c 0.3
python train_day41.py --dataset-path data/sentiment_v1.csv --dataset-version sentiment-v1 --classifier-c 1.0
python train_day41.py --dataset-path data/sentiment_v1.csv --dataset-version sentiment-v1 --classifier-c 3.0
7. Promote candidate thành champion
Không nên tự động promote chỉ vì một run có metric cao nhất. Nên có bước validation và approval riêng.
File gợi ý: promote_model.py.
import argparse
import mlflow
from mlflow import MlflowClient
def promote(tracking_uri: str, model_name: str, source_alias: str, target_alias: str) -> None:
mlflow.set_tracking_uri(tracking_uri)
client = MlflowClient()
candidate = client.get_model_version_by_alias(model_name, source_alias)
if candidate.tags.get("release_gate") != "passed":
raise RuntimeError(
f"Model version {candidate.version} chưa qua release gate: "
f"{candidate.tags.get('release_gate')}"
)
client.set_model_version_tag(
name=model_name,
version=candidate.version,
key="approval_status",
value="approved",
)
client.set_registered_model_alias(
name=model_name,
alias=target_alias,
version=candidate.version,
)
print(f"Promoted {model_name} version {candidate.version} to @{target_alias}")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--tracking-uri", default="http://127.0.0.1:5000")
parser.add_argument("--model-name", default="sentiment-classifier")
parser.add_argument("--source-alias", default="candidate")
parser.add_argument("--target-alias", default="champion")
args = parser.parse_args()
promote(args.tracking_uri, args.model_name, args.source_alias, args.target_alias)
Ứng dụng serving nên load model bằng alias ổn định:
import mlflow.pyfunc
model = mlflow.pyfunc.load_model("models:/sentiment-classifier@champion")
predictions = model.predict(["Dịch vụ hỗ trợ rất nhanh"])
Rollback là thao tác trỏ alias champion về version trước đó, không phải rebuild model trong lúc incident.
8. Tracking cho LoRA, LLM và RAG
Với Day 27 LoRA, model artifact thường là adapter, không phải full base model. Cần log thêm:
| Thành phần | Cần log |
|---|---|
| Base model | base_model_id, base_model_revision, license |
| Adapter | LoRA rank, alpha, dropout, target modules |
| Dataset | instruction dataset version/hash, filtering rule |
| Prompt | chat template version, system prompt version |
| Eval | task metrics, safety eval, latency, VRAM, tokens/sec |
| Serving | quantization, max tokens, batch size |
Với RAG, "model version" không chỉ là LLM. Cần tracking cả pipeline:
corpus_version
chunking_strategy
embedding_model
embedding_dimension
index_version
retriever_config
reranker_model
prompt_version
llm_model
eval_set_version
Nếu đổi chunk size hoặc embedding model mà không log index_version, regression RAG rất khó debug.
9. Trade-off quan trọng
| Lựa chọn | Điểm mạnh | Điểm yếu | Khi nào chọn |
|---|---|---|---|
| Log đầy đủ artifacts | Debug và audit tốt | Tốn storage, có rủi ro PII | Production hoặc regulated workflow |
| Chỉ log summary metrics | Nhanh, rẻ | Khó reproduce và rollback | Prototype rất ngắn hạn |
| Local MLflow | Dễ học, không phụ thuộc SaaS | Không có backup/auth/collaboration tốt | Bài học, portfolio, solo project |
| Self-host MLflow | Kiểm soát dữ liệu tốt | Cần vận hành DB, artifact store, auth | Team có yêu cầu data residency |
| Managed SaaS như W&B/Neptune | Collaboration và dashboard mạnh | Cost, vendor policy, data egress | Team research/product cần UI mạnh |
| Manual promotion | Kiểm soát chặt | Dễ quên bước, chậm | Model có rủi ro business cao |
| Automated promotion | Nhanh, consistent | Gate sai có thể đẩy model lỗi | Metric ổn định, có canary/rollback |
Best default cho khóa học: MLflow local/self-host, log đầy đủ run metadata, dùng aliases candidate và champion, chỉ promote sau khi có decision note. Với team production nhỏ, self-host MLflow + Postgres + object storage là lựa chọn cân bằng giữa cost, control và đủ tính năng.
10. Performance, cost, security, reproducibility
Performance concerns:
- Log artifact quá lớn trong training loop có thể làm chậm job.
- UI query chậm nếu backend store phình to và không có retention policy.
- Registry alias chỉ giải quyết chọn model version; serving vẫn cần cache model và warm-up riêng.
- Model tốt nhất theo
macro_f1có thể không đạt latency/cost gate.
Cost concerns:
- Artifact store tăng nhanh nếu log checkpoint mỗi epoch.
- LLM/RAG eval có thể tốn tiền nếu replay golden set lớn bằng provider managed.
- Managed tracking tool tính phí theo user, run, artifact hoặc storage.
Security concerns:
- Không log API key, token, raw PII, customer text nhạy cảm vào params/tags/artifacts.
- Tracking UI phải có authentication, authorization, TLS và network boundary.
- Artifact store cần encryption, IAM least privilege và lifecycle policy.
- Model artifact có thể chứa serialized code; không load model từ registry không tin cậy.
Reproducibility concerns:
seed=42chưa đủ nếu thiếu dataset hash, split strategy, dependency versions và hardware note.- Base model hoặc tokenizer trên Hugging Face cần pin revision, không chỉ pin model id.
- Evaluation script cũng phải version, vì đổi metric code có thể đổi kết luận.
- Với RAG, phải version index và prompt như version model.
11. Dùng được trong production không?
Có, MLflow dùng được trong production nếu đáp ứng các điều kiện sau:
- Tracking Server chạy trên hạ tầng có backup, monitoring, authentication, TLS và access control.
- Backend store dùng Postgres/MySQL hoặc service tương đương, không dùng SQLite local cho nhiều người dùng.
- Artifact store dùng object storage bền vững như S3/GCS/Azure Blob, có encryption và lifecycle policy.
- Training pipeline bắt buộc log dataset version/hash, code commit, params, metrics, artifacts và model signature.
- Model Registry alias là contract với deployment, ví dụ serving chỉ đọc
models:/sentiment-classifier@champion. - Promotion có release gate: quality metric, latency, cost, security review, data leakage check và rollback target.
- Không log dữ liệu nhạy cảm chưa redact.
- Có monitoring sau deploy để phát hiện drift, latency regression, error rate và business metric regression.
Không nên xem MLflow là toàn bộ production platform. MLflow quản lý lineage, artifact và registry; bạn vẫn cần CI/CD, serving infra, observability, security, data validation và incident process.
12. Kết quả cần nộp
Cuối Day 41, bạn nên có:
- MLflow UI có ít nhất 3 runs.
- Mỗi run có params, metrics, dataset inputs, artifacts và logged model.
- Một registered model tên rõ, ví dụ
sentiment-classifier. - Alias
candidatetrỏ tới version đã qua gate. - Alias
championtrỏ tới version được approve để serve. model_card.md,eval_summary.jsonvà decision note.- Rollback plan: version trước đó là gì và lệnh nào đổi alias về version đó.
Tài liệu
1. Tracking schema chuẩn
Một run đủ tốt cho production review nên có schema tối thiểu:
| Field | Ví dụ | Bắt buộc? |
|---|---|---|
experiment_name | day41-sentiment-classifier | Có |
run_name | sentiment-v1-a1b2c3d4 | Có |
registered_model_name | sentiment-classifier | Có nếu có deploy |
git_commit | a1b2c3d4... | Có |
dataset_version | sentiment-v1 | Có |
dataset_hash | sha256:... | Có |
split_strategy | stratified_80_20_seed_42 | Có |
primary_metric | macro_f1 | Có |
release_gate | passed hoặc failed | Có |
owner | ai-team | Có |
approval_status | pending, approved, rejected | Có với registry |
Naming convention gợi ý:
experiment: <phase>-<task>-<model-family>
run_name: <dataset-version>-<model-config>-<git-short-sha>
model: <business-task>-<model-role>
alias: candidate | champion | shadow | rollback
Ví dụ:
experiment: day41-vietnamese-sentiment
run_name: sentiment-v1-tfidf-lr-a1b2c3d4
model: sentiment-classifier
alias: champion
2. Artifact structure
Artifact nên có cấu trúc đọc được bằng người và máy:
artifacts/day41/
classification_report.json
confusion_matrix.png
eval_summary.json
model_card.md
sample_predictions_redacted.jsonl
requirements-lock.txt
eval_summary.json nên có:
{
"run_id": "abc123",
"registered_model_name": "sentiment-classifier",
"model_version": "3",
"dataset_version": "sentiment-v1",
"dataset_hash": "sha256:...",
"git_commit": "a1b2c3d4",
"primary_metric": "macro_f1",
"metrics": {
"macro_f1": 0.84,
"accuracy": 0.86,
"p95_latency_ms": 18.5
},
"release_gate": {
"macro_f1_min": 0.80,
"p95_latency_ms_max": 30,
"status": "passed"
}
}
3. Model card template
# Model Card: <registered_model_name>
## Intended Use
<Bài toán model được phép xử lý.>
## Not Intended Use
<Các tình huống không được dùng hoặc chưa được kiểm chứng.>
## Training Data
- Dataset version:
- Dataset hash:
- Split strategy:
- Data source:
- PII handling:
## Evaluation
- Primary metric:
- Secondary metrics:
- Latency:
- Cost estimate:
- Error analysis:
## Model Version
- Registered model:
- Version:
- Alias:
- Source run:
- Git commit:
## Limitations
- <Known limitation 1>
- <Known limitation 2>
## Production Conditions
- <Điều kiện hạ tầng, monitoring, security, fallback.>
## Rollback
- Previous champion version:
- Rollback command:
4. Release gate mẫu
Release gate phải phụ thuộc context. Không có một metric chung cho mọi model.
| Loại model | Primary gate | Secondary gate | Gate vận hành |
|---|---|---|---|
| Classification mất cân bằng | macro_f1, per-class recall | calibration, confusion matrix | p95 latency, memory |
| Fraud/risk | recall ở class rủi ro | precision, false positive cost | audit trail, explainability |
| Search/RAG | recall@k, MRR, citation accuracy | answer faithfulness, no-answer accuracy | token cost, p95 latency |
| LoRA/LLM task | task success rate | safety eval, refusal accuracy | tokens/sec, VRAM, cost |
Ví dụ gate cho Day 16 sentiment:
quality:
macro_f1_min: 0.80
negative_recall_min: 0.75
performance:
p95_latency_ms_max: 30
security:
pii_in_artifacts: false
reproducibility:
requires_dataset_hash: true
requires_git_commit: true
approval:
reviewer: required
5. Registry lifecycle
Workflow đề xuất:
run training
-> log params/metrics/artifacts/model
-> register model version
-> set version tag validation_status=candidate
-> offline validation
-> set alias candidate
-> reviewer approves
-> set alias champion
-> deploy serving reads @champion
-> monitor
-> rollback alias if regression
Không dùng alias như decoration. Alias phải là contract mà serving thật sự đọc.
6. Runbook: chọn best run
- Mở MLflow UI và lọc đúng experiment.
- Sắp xếp theo primary metric, ví dụ
macro_f1. - Loại các run không cùng dataset version hoặc không cùng eval set.
- Kiểm tra metric phụ: per-class recall, latency, memory, cost.
- Mở artifacts:
classification_report.json, confusion matrix, model card. - Xác nhận run có
git_commit,dataset_hash,split_strategy. - So sánh với champion hiện tại, không chỉ so với các run mới.
- Nếu tốt hơn và qua gate, register hoặc giữ model version đã auto-register.
- Gắn tag
release_gate=passed. - Gắn alias
candidate.
Decision rule ví dụ:
Chỉ chọn run mới nếu:
- Macro F1 tăng ít nhất 1 điểm phần trăm hoặc sửa lỗi class quan trọng.
- Không làm p95 latency vượt gate.
- Không tăng cost vượt budget.
- Không có regression nghiêm trọng trong error analysis.
7. Runbook: promote
Trước khi promote:
- Candidate qua release gate.
- Model card đã cập nhật.
- Artifact không chứa raw PII hoặc secret.
- Dataset và code version đầy đủ.
- Serving đã smoke test model URI
models:/<name>@candidate. - Rollback target đã xác định.
- Reviewer approve.
Promote:
from mlflow import MlflowClient
client = MlflowClient()
model_name = "sentiment-classifier"
candidate = client.get_model_version_by_alias(model_name, "candidate")
client.set_model_version_tag(
name=model_name,
version=candidate.version,
key="approval_status",
value="approved",
)
client.set_registered_model_alias(
name=model_name,
alias="champion",
version=candidate.version,
)
Smoke test sau promote:
import mlflow.pyfunc
model = mlflow.pyfunc.load_model("models:/sentiment-classifier@champion")
result = model.predict(["Ứng dụng hoạt động ổn định sau bản cập nhật"])
print(result)
8. Runbook: rollback
Rollback nên đổi alias, không retrain trong lúc incident.
Các bước:
- Xác nhận incident: metric, latency, error rate hoặc business KPI regression.
- Tìm previous champion version từ deployment log hoặc MLflow UI.
- Gắn tag cho version lỗi:
incident_status=rollback_requested. - Trỏ alias
championvề previous version. - Restart hoặc reload serving nếu service không tự refresh model alias.
- Ghi incident note: thời gian, version lỗi, version rollback, nguyên nhân ban đầu, follow-up.
Lệnh rollback:
from mlflow import MlflowClient
client = MlflowClient()
client.set_registered_model_alias(
name="sentiment-classifier",
alias="champion",
version="2",
)
client.set_model_version_tag(
name="sentiment-classifier",
version="3",
key="incident_status",
value="rolled_back",
)
9. Security checklist
- Tracking UI không public internet nếu chưa có auth.
- Dùng TLS khi truy cập từ network khác machine local.
- Artifact store có encryption at rest.
- IAM cho training job chỉ được ghi vào artifact prefix cần thiết.
- Không log API key, database URL có password, bearer token hoặc private key.
- Không log raw PII trong sample predictions.
- Dataset source nhạy cảm không ghi vào tag nếu tag có nhiều người đọc.
- Model artifact đến từ registry không tin cậy không được load vào service production.
- Có retention policy cho artifacts và runs cũ.
10. Reproducibility checklist
- Có
git_commit. - Có
dataset_versionvàdataset_hash. - Có train/validation/test split strategy.
- Có seed và note về nondeterminism nếu dùng GPU.
- Có package versions hoặc lock file.
- Có base model id và revision nếu dùng pretrained model.
- Có tokenizer version hoặc tokenizer artifact.
- Có evaluation script version.
- Có model signature và input example.
- Có model card mô tả limitation.
11. Performance và cost checklist
- Log
p50_latency_ms,p95_latency_ms, batch size và hardware. - Không log checkpoint lớn không cần thiết.
- Có lifecycle policy cho artifact store.
- Có cost estimate nếu dùng managed LLM/embedding/reranker.
- Có budget gate cho replay eval hoặc batch inference.
- Serving load model bằng alias ổn định và có warm-up.
- Có benchmark trên input gần production, không chỉ sample rất ngắn.
12. MLflow vs W&B vs Neptune
| Tool | Mạnh nhất khi | Điểm cần cân nhắc | Best context |
|---|---|---|---|
| MLflow | Muốn open-source, self-host, tracking + registry + artifact | UI collaboration không mạnh bằng SaaS chuyên dụng | MLOps baseline, portfolio, team cần kiểm soát dữ liệu |
| W&B | Cần dashboard đẹp, collaboration research, rich media | SaaS/data policy/cost | Team train nhiều model và cần phân tích experiment mạnh |
| Neptune | Cần metadata management cho ML team | Adoption thấp hơn MLflow/W&B ở nhiều org | Team ML chuyên sâu cần quản trị metadata |
| Custom tracking | Domain rất đặc thù | Dễ thiếu registry, lineage, UI và governance | Chỉ nên làm khi requirement vượt hẳn tool sẵn có |
Best solution theo context:
- Học tập hoặc capstone: MLflow local với SQLite và local artifacts.
- Team nhỏ cần production baseline: MLflow self-host, Postgres, S3-compatible artifact store, auth qua reverse proxy.
- Team research-heavy: W&B hoặc Neptune có thể tốt hơn cho collaboration, nhưng vẫn cần policy về data egress.
- Enterprise nhiều environment: tách registry/dev/prod hoặc dùng alias/environment naming rõ, kết hợp CI/CD và approval workflow.
13. Production readiness answer template
## Dùng được trong production không?
Có/Chưa.
### Nếu có, điều kiện bắt buộc
- Tracking server:
- Backend store:
- Artifact store:
- Auth/TLS:
- Dataset/code lineage:
- Release gate:
- Monitoring:
- Rollback:
### Rủi ro còn lại
- Performance:
- Cost:
- Security:
- Reproducibility:
- Model quality:
### Quyết định release
- Champion hiện tại:
- Candidate:
- Metric thay đổi:
- Người approve:
- Rollback version:
14. Nguồn API đã kiểm chứng
- MLflow Model Registry hiện tại dùng registered models, model versions, aliases và tags; aliases phù hợp để deployment đọc
models:/<model-name>@<alias>. - MLflow Model Registry stages đã bị deprecate từ MLflow 2.9.0; không nên xây workflow mới quanh
Staging/Productionstages. mlflow.data.from_pandas(..., targets=...)vàmlflow.log_input(..., context=...)dùng để log dataset metadata vào run.
Tham khảo chính thức:
- https://mlflow.org/docs/latest/ml/model-registry/
- https://mlflow.org/docs/latest/ml/model-registry/workflow/
- https://mlflow.org/docs/latest/python_api/mlflow.data.html
Bài tập
Mục tiêu
Bạn sẽ track lại một training workflow từ Day 16 hoặc Day 27 bằng MLflow, sau đó register model và viết release decision.
Chọn một trong hai hướng:
- Dễ kiểm soát: Day 16 sentiment classifier với scikit-learn hoặc Transformers.
- Nâng cao: Day 27 LoRA adapter, log thêm base model revision, adapter config và eval latency/tokens per second.
Thời lượng đề xuất:
- Bản tối thiểu: 60-90 phút.
- Bản portfolio tốt: 3-4 giờ.
- Bản gần production: 1 ngày, có CI script, model card, rollback và monitoring stub.
0. Acceptance criteria
Hoàn thành bài tập khi bạn có:
- MLflow Tracking UI chạy local.
- Ít nhất 3 runs trong cùng experiment.
- Mỗi run log params, metrics, artifacts, dataset metadata và code commit.
- Có artifact
classification_report.jsonhoặc eval report tương đương. - Có model signature và input example.
- Có registered model, ví dụ
sentiment-classifier. - Có alias
candidatecho version qua release gate. - Có alias
championsau khi promote. - Có decision note trả lời production readiness.
- Có rollback plan rõ version và command.
1. Chuẩn bị
Tạo structure:
day41-mlflow-lab/
data/
sentiment_v1.csv
artifacts/
train_day41.py
promote_model.py
decision-note.md
README.md
Cài package:
python3 -m venv .venv
source .venv/bin/activate
pip install -U mlflow scikit-learn pandas matplotlib
Chạy MLflow:
mkdir -p mlruns mlartifacts
mlflow server \
--host 127.0.0.1 \
--port 5000 \
--backend-store-uri sqlite:///mlruns/mlflow.db \
--default-artifact-root ./mlartifacts
2. Tạo dataset tối thiểu
Dataset cần ít nhất 20 dòng, có cột text và label.
Ví dụ format:
text,label
"Giao hàng nhanh và đóng gói cẩn thận",positive
"Ứng dụng bị lỗi khi đăng nhập",negative
"Dịch vụ ở mức chấp nhận được",neutral
Yêu cầu:
- Có ít nhất 2 classes; tốt hơn là 3 classes.
- Mỗi class có đủ sample để split stratified.
- Không chứa PII thật.
- Ghi rõ dataset version, ví dụ
sentiment-v1.
3. Implement tracking
Dựa vào code trong lession.md, tạo train_day41.py.
Bạn có thể dùng scikit-learn baseline hoặc model từ Day 16. Bắt buộc log:
Params:
model_typedataset_versiondataset_hashrandom_state- hyperparameters chính
Metrics:
accuracymacro_f1weighted_f1p95_latency_ms
Artifacts:
classification_report.jsoneval_summary.jsonmodel_card.md- optional:
confusion_matrix.png
Tags:
git_commitownertaskapproval_status
Dataset:
mlflow.data.from_pandas(...)mlflow.log_input(..., context="training")mlflow.log_input(..., context="evaluation")
4. Chạy 3 runs
Ví dụ:
python train_day41.py \
--dataset-path data/sentiment_v1.csv \
--dataset-version sentiment-v1 \
--classifier-c 0.3
python train_day41.py \
--dataset-path data/sentiment_v1.csv \
--dataset-version sentiment-v1 \
--classifier-c 1.0
python train_day41.py \
--dataset-path data/sentiment_v1.csv \
--dataset-version sentiment-v1 \
--classifier-c 3.0
Trong MLflow UI, so sánh:
macro_f1- per-class recall trong
classification_report.json p95_latency_ms- dataset hash có giống nhau không
- git commit có giống hoặc được ghi rõ không
5. Chọn best run
Không chọn best run chỉ vì accuracy cao.
Quy tắc gợi ý:
Candidate được chọn nếu:
- macro_f1 cao nhất hoặc tốt hơn champion hiện tại ít nhất 1 điểm phần trăm.
- Không class quan trọng nào có recall quá thấp.
- p95_latency_ms không vượt 30 ms với batch nhỏ local.
- Artifact đầy đủ và không chứa PII.
- Có dataset_hash và git_commit.
Viết vào decision-note.md:
# Day 41 Release Decision
## Candidate
- Registered model:
- Version:
- Source run:
- Dataset version:
- Dataset hash:
- Git commit:
## Metrics
- Macro F1:
- Accuracy:
- Weighted F1:
- P95 latency:
## So sánh với baseline/champion
- Metric tăng/giảm:
- Latency tăng/giảm:
- Cost/storage impact:
## Quyết định
Promote/Không promote.
## Lý do
<Giải thích ngắn, dựa trên metric và limitation.>
## Rollback
- Previous champion version:
- Rollback command:
6. Register và promote
Nếu code dùng registered_model_name trong mlflow.sklearn.log_model, model version sẽ được register tự động.
Gắn alias candidate cho version qua gate:
from mlflow import MlflowClient
client = MlflowClient()
client.set_registered_model_alias(
name="sentiment-classifier",
alias="candidate",
version="1",
)
Promote thành champion sau khi review:
from mlflow import MlflowClient
client = MlflowClient()
candidate = client.get_model_version_by_alias("sentiment-classifier", "candidate")
client.set_registered_model_alias(
name="sentiment-classifier",
alias="champion",
version=candidate.version,
)
Load thử model bằng alias:
import mlflow.pyfunc
model = mlflow.pyfunc.load_model("models:/sentiment-classifier@champion")
print(model.predict(["Sản phẩm tốt, hỗ trợ nhanh"]))
7. Rollback drill
Giả lập candidate/champion version mới bị lỗi.
Yêu cầu:
- Tìm previous champion version.
- Trỏ alias
championvề previous version. - Gắn tag
incident_status=rolled_backcho version lỗi. - Ghi lại trong
decision-note.md.
Ví dụ:
from mlflow import MlflowClient
client = MlflowClient()
client.set_registered_model_alias(
name="sentiment-classifier",
alias="champion",
version="1",
)
client.set_model_version_tag(
name="sentiment-classifier",
version="2",
key="incident_status",
value="rolled_back_in_lab",
)
8. Nếu làm với Day 27 LoRA
Log thêm params:
base_model_idbase_model_revisiontokenizer_revisionlora_rlora_alphalora_dropouttarget_modulesmax_seq_lengthgradient_accumulation_stepsquantization
Log thêm metrics:
eval_loss- task metric chính
tokens_per_secondp95_latency_msvram_peak_gb
Artifacts:
- adapter files
- tokenizer files nếu cần
- training config
- eval report
- model card
Production note cho LoRA:
Adapter không đủ để reproduce nếu thiếu base model id/revision, tokenizer revision,
prompt/chat template version và serving config.
9. Câu hỏi bắt buộc
Trả lời trong README hoặc decision-note.md:
- Dùng workflow này được trong production không? Nếu có thì cần điều kiện gì?
- Nếu MLflow server mất dữ liệu, model đang chạy có bị ảnh hưởng không? Vì sao?
- Vì sao không nên dùng test set để chọn hyperparameters?
- Nếu run tốt nhất có
macro_f1cao hơn nhưng latency gấp 5 lần, bạn có promote không? - Nếu artifact có sample prediction chứa email khách hàng, bạn xử lý thế nào?
- Rollback bằng alias khác gì rollback bằng retrain?
- Với RAG, bạn sẽ log thêm những version nào ngoài LLM model?
10. Production readiness answer mẫu
## Dùng được trong production không?
Chưa, nếu chỉ chạy local bằng SQLite và local artifact store.
Có thể dùng trong production nếu:
- MLflow Tracking Server được deploy sau auth/TLS.
- Backend store dùng Postgres/MySQL có backup.
- Artifact store dùng S3/GCS/Azure Blob có encryption và lifecycle policy.
- Training pipeline bắt buộc log dataset hash, code commit, params, metrics, artifacts và model signature.
- Promotion qua release gate và reviewer approval.
- Serving load model bằng `models:/sentiment-classifier@champion`.
- Có monitoring cho drift, latency, error rate và business metric.
- Có rollback alias về previous champion.
Rủi ro còn lại:
- Dataset nhỏ nên metric chưa ổn định.
- Chưa có drift monitoring.
- Chưa có security review đầy đủ cho artifact.
- Chưa benchmark trên traffic thật.
11. Nộp bài
Nộp các bằng chứng:
- Screenshot hoặc mô tả MLflow UI có 3 runs.
- Run ID của best run.
- Registered model name và version.
- Alias hiện tại của
candidatevàchampion. classification_report.json.model_card.md.decision-note.md.- Rollback command đã test.