Published on

Day 41: MLflow, Experiment Tracking Và Model Registry

Authors

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àmCâu hỏi cần trả lờiMetadata cần có
ReproduceCó chạy lại ra kết quả tương đương không?code commit, dataset version/hash, config, seed, package versions
CompareRun nào tốt hơn trong cùng điều kiện?metric chính, metric phụ, eval set, latency, cost
Deploy/RollbackVersion 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ómVí dụLý do
Paramsmodel_type, learning_rate, max_features, epochs, batch_size, seedSo sánh cấu hình và reproduce
Metricsaccuracy, macro_f1, eval_loss, p95_latency_ms, cost_per_1k_predictionsChọn best run theo business gate
Artifactsclassification_report.json, confusion_matrix.png, model_card.md, eval_summary.jsonReview bằng người, audit và debug
Datasetdataset_version, dataset_hash, train/validation/test splitTránh so sánh lệch dữ liệu
Codegit_commit, training_script, package lockBiết code nào sinh model
Tagsowner, task, environment, approval_statusTì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ầnCần log
Base modelbase_model_id, base_model_revision, license
AdapterLoRA rank, alpha, dropout, target modules
Datasetinstruction dataset version/hash, filtering rule
Promptchat template version, system prompt version
Evaltask metrics, safety eval, latency, VRAM, tokens/sec
Servingquantization, 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ếuKhi nào chọn
Log đầy đủ artifactsDebug và audit tốtTốn storage, có rủi ro PIIProduction hoặc regulated workflow
Chỉ log summary metricsNhanh, rẻKhó reproduce và rollbackPrototype rất ngắn hạn
Local MLflowDễ học, không phụ thuộc SaaSKhông có backup/auth/collaboration tốtBài học, portfolio, solo project
Self-host MLflowKiểm soát dữ liệu tốtCần vận hành DB, artifact store, authTeam có yêu cầu data residency
Managed SaaS như W&B/NeptuneCollaboration và dashboard mạnhCost, vendor policy, data egressTeam research/product cần UI mạnh
Manual promotionKiểm soát chặtDễ quên bước, chậmModel có rủi ro business cao
Automated promotionNhanh, consistentGate sai có thể đẩy model lỗiMetric ổn định, có canary/rollback

Best default cho khóa học: MLflow local/self-host, log đầy đủ run metadata, dùng aliases candidatechampion, 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_f1 có 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=42 chư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 candidate trỏ tới version đã qua gate.
  • Alias champion trỏ tới version được approve để serve.
  • model_card.md, eval_summary.json và 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:

FieldVí dụBắt buộc?
experiment_nameday41-sentiment-classifier
run_namesentiment-v1-a1b2c3d4
registered_model_namesentiment-classifierCó nếu có deploy
git_commita1b2c3d4...
dataset_versionsentiment-v1
dataset_hashsha256:...
split_strategystratified_80_20_seed_42
primary_metricmacro_f1
release_gatepassed hoặc failed
ownerai-team
approval_statuspending, approved, rejectedCó 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 modelPrimary gateSecondary gateGate vận hành
Classification mất cân bằngmacro_f1, per-class recallcalibration, confusion matrixp95 latency, memory
Fraud/riskrecall ở class rủi roprecision, false positive costaudit trail, explainability
Search/RAGrecall@k, MRR, citation accuracyanswer faithfulness, no-answer accuracytoken cost, p95 latency
LoRA/LLM tasktask success ratesafety eval, refusal accuracytokens/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

  1. Mở MLflow UI và lọc đúng experiment.
  2. Sắp xếp theo primary metric, ví dụ macro_f1.
  3. Loại các run không cùng dataset version hoặc không cùng eval set.
  4. Kiểm tra metric phụ: per-class recall, latency, memory, cost.
  5. Mở artifacts: classification_report.json, confusion matrix, model card.
  6. Xác nhận run có git_commit, dataset_hash, split_strategy.
  7. So sánh với champion hiện tại, không chỉ so với các run mới.
  8. Nếu tốt hơn và qua gate, register hoặc giữ model version đã auto-register.
  9. Gắn tag release_gate=passed.
  10. 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:

  1. Xác nhận incident: metric, latency, error rate hoặc business KPI regression.
  2. Tìm previous champion version từ deployment log hoặc MLflow UI.
  3. Gắn tag cho version lỗi: incident_status=rollback_requested.
  4. Trỏ alias champion về previous version.
  5. Restart hoặc reload serving nếu service không tự refresh model alias.
  6. 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

  • git_commit.
  • dataset_versiondataset_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

ToolMạnh nhất khiĐiểm cần cân nhắcBest context
MLflowMuốn open-source, self-host, tracking + registry + artifactUI collaboration không mạnh bằng SaaS chuyên dụngMLOps baseline, portfolio, team cần kiểm soát dữ liệu
W&BCần dashboard đẹp, collaboration research, rich mediaSaaS/data policy/costTeam train nhiều model và cần phân tích experiment mạnh
NeptuneCần metadata management cho ML teamAdoption thấp hơn MLflow/W&B ở nhiều orgTeam ML chuyên sâu cần quản trị metadata
Custom trackingDomain rất đặc thùDễ thiếu registry, lineage, UI và governanceChỉ 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/Production stages.
  • mlflow.data.from_pandas(..., targets=...)mlflow.log_input(..., context=...) dùng để log dataset metadata vào run.

Tham khảo chính thức:


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.json hoặc eval report tương đương.
  • Có model signature và input example.
  • Có registered model, ví dụ sentiment-classifier.
  • Có alias candidate cho version qua release gate.
  • Có alias champion sau 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 textlabel.

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_type
  • dataset_version
  • dataset_hash
  • random_state
  • hyperparameters chính

Metrics:

  • accuracy
  • macro_f1
  • weighted_f1
  • p95_latency_ms

Artifacts:

  • classification_report.json
  • eval_summary.json
  • model_card.md
  • optional: confusion_matrix.png

Tags:

  • git_commit
  • owner
  • task
  • approval_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  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 champion về previous version.
  • Gắn tag incident_status=rolled_back cho 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_id
  • base_model_revision
  • tokenizer_revision
  • lora_r
  • lora_alpha
  • lora_dropout
  • target_modules
  • max_seq_length
  • gradient_accumulation_steps
  • quantization

Log thêm metrics:

  • eval_loss
  • task metric chính
  • tokens_per_second
  • p95_latency_ms
  • vram_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:

  1. Dùng workflow này được trong production không? Nếu có thì cần điều kiện gì?
  2. Nếu MLflow server mất dữ liệu, model đang chạy có bị ảnh hưởng không? Vì sao?
  3. Vì sao không nên dùng test set để chọn hyperparameters?
  4. Nếu run tốt nhất có macro_f1 cao hơn nhưng latency gấp 5 lần, bạn có promote không?
  5. Nếu artifact có sample prediction chứa email khách hàng, bạn xử lý thế nào?
  6. Rollback bằng alias khác gì rollback bằng retrain?
  7. 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 candidatechampion.
  • classification_report.json.
  • model_card.md.
  • decision-note.md.
  • Rollback command đã test.