PABCD Initiative Documentation Hub pabcd_initiative codexclaw cli-jaw

ML Pipeline Engineering

Source: dev-data/references/ml-pipeline.md

ML Pipeline Engineering

Last reviewed: 2026-06-16

Applies to: Python (MLflow 3.x, Dagster 1.9+, DVC 3.x), GPU training (PyTorch 2.6+)

When to read: ML training pipelines, experiment tracking, feature stores, data versioning, task_tags: ml

Canonical owner: dev-data — pipeline orchestration, data versioning, feature engineering

Non-goals: Model serving (→ dev-backend/references/core/ml-serving.md), LLM integration (→ dev-backend/references/core/llm-integration.md), model evaluation (→ dev-testing/references/ml-evaluation.md)

---

§1 ML Pipeline Principles

PrincipleWhat It Means
ReproducibilitySame code + same data + same config = same model. Pin library versions, fix random seeds, log all hyperparameters.
Version everythingCode (git), data (DVC/Delta), models (MLflow registry), configs (hydra/yaml in repo).
Experiment trackingEvery training run logged with metrics, params, artifacts. No "which run was that?" moments.
Idempotent pipelinesRe-running a pipeline stage with same inputs produces same outputs. Use content-addressable caching.
Fail fastValidate data schema and distributions BEFORE training. Don't waste GPU hours on bad data.

---

§2 Experiment Tracking

PlatformBest ForHosting2026 Status
MLflow 3.xFull lifecycle (track → register → serve)Self-hosted / Databricks✅ Default — unified API, broad ecosystem
W&B (Weights & Biases)Rich visualization, team collaborationCloud (free tier) / self-hosted✅ Best UI, expensive at scale
Dagster+Pipeline-integrated trackingDagster Cloud / self-hosted✅ If already using Dagster for orchestration

MLflow 3.x Tracking Pattern

import mlflow

mlflow.set_tracking_uri("http://mlflow.internal:5000")
mlflow.set_experiment("recommender-v2")

with mlflow.start_run(run_name="baseline-transformer"):
    mlflow.log_params({
        "model": "transformer",
        "lr": 1e-4,
        "batch_size": 64,
        "epochs": 50,
        "data_version": "v2.3.1",  # DVC tag or Delta version
    })

    for epoch in range(50):
        metrics = train_one_epoch(model, train_loader)
        mlflow.log_metrics(metrics, step=epoch)

        if epoch % 10 == 0:
            # Checkpoint every 10 epochs
            mlflow.pytorch.log_model(model, f"checkpoint-{epoch}")

    # Log final model to registry
    mlflow.pytorch.log_model(
        model,
        "model",
        registered_model_name="recommender",
    )
    mlflow.log_artifact("configs/train.yaml")

Decision Rule

IF   need full lifecycle (track + register + deploy)  → MLflow 3.x
IF   team wants rich dashboards + collaboration       → W&B (log to both if needed)
IF   pipeline orchestration already in Dagster         → use Dagster+ tracking

---

§3 Feature Store

PlatformOnline ServingOffline Training2026 Status
Feast 0.40+Redis, DynamoDB, PostgreSQLBigQuery, Snowflake, file✅ Default — open source, flexible
TectonManaged low-latencyManaged✅ Enterprise, expensive
CustomRedis/PostgreSQLSQL warehouse⚠️ Only if <10 features

Feast Pattern

from feast import FeatureStore

store = FeatureStore(repo_path="feature_repo/")

# Offline: training data retrieval (point-in-time correct)
training_df = store.get_historical_features(
    entity_df=entity_df,  # DataFrame with entity keys + timestamps
    features=[
        "user_features:purchase_count_30d",
        "user_features:avg_session_duration",
        "item_features:category_embedding",
    ],
).to_df()

# Online: real-time serving
features = store.get_online_features(
    features=["user_features:purchase_count_30d"],
    entity_rows=[{"user_id": "abc123"}],
).to_dict()

When to Use a Feature Store

ConditionFeature Store?
Same features used in training AND serving✅ Yes — prevents train/serve skew
>20 ML features shared across models✅ Yes — single source of truth
<10 features, single model❌ Overkill — compute inline
Real-time features from streaming sources✅ Yes — Feast + Kafka materialization

---

§4 Training Pipeline Orchestration

Dagster DAG Pattern

from dagster import asset, define_asset_job, Definitions
import mlflow

@asset
def raw_data():
    """Extract raw data from source."""
    return pd.read_parquet("s3://data/raw/events.parquet")

@asset
def processed_data(raw_data):
    """Validate schema, clean, feature-engineer."""
    validated = validate_schema(raw_data, expected_schema)
    cleaned = remove_outliers(validated)
    return engineer_features(cleaned)

@asset
def trained_model(processed_data):
    """Train model with experiment tracking."""
    with mlflow.start_run():
        model = train(processed_data)
        metrics = evaluate(model, test_split(processed_data))
        mlflow.log_metrics(metrics)
        mlflow.pytorch.log_model(model, "model",
            registered_model_name="my-model")
    return model

training_job = define_asset_job("training_pipeline",
    selection=["raw_data", "processed_data", "trained_model"])

defs = Definitions(
    assets=[raw_data, processed_data, trained_model],
    jobs=[training_job],
)

GPU Resource Management

PracticeWhy
Request specific GPU type in job configAvoid training on wrong GPU (A10G vs H100)
Set CUDA_VISIBLE_DEVICESPrevent multi-tenant GPU conflicts
Checkpoint every N epochsResume after preemption; don't lose hours of work
Use mixed precision (torch.amp)2× speedup, half memory — no quality loss on modern GPUs
Set max_epochs + early stoppingPrevent runaway training costs

---

§5 Data Versioning

ToolBest For2026 Status
DVC 3.xFile-based datasets (images, CSVs, Parquet)✅ Default — git-like workflow
Delta Lake 3.xTabular data, time travel, ACID transactions✅ Best for data lake/warehouse
LakeFSGit-like branching for data lakes✅ Advanced — multi-branch experimentation

DVC Workflow

# Track a large dataset
dvc add data/training/images/
git add data/training/images.dvc .gitignore
git commit -m "data: add training images v1"
git tag data-v1.0

# Reproduce pipeline
dvc repro  # Runs only changed stages (content-addressable)

# Switch to a different data version
git checkout data-v2.0
dvc checkout  # Pulls the matching data version from remote

Version Linking Checklist

  • [ ] Every training run logs data_version (DVC tag or Delta version) in experiment tracker
  • [ ] Model registry entry includes data version used for training
  • [ ] Promotion to production requires matching data version documented
  • [ ] Data schema changes trigger model retraining CI job

---

§6 Anti-Patterns

BannedSymptomFix
Training without experiment tracking"Which hyperparams worked?" — nobody knowsMLflow/W&B from day one (§2)
Unversioned training dataNon-reproducible modelsDVC or Delta Lake (§5)
Feature computation in both training and serving codeTrain/serve skew, silent accuracy dropsFeature store (§3)
No checkpointingLost GPU hours on preemptionCheckpoint every N epochs (§4)
Manual GPU allocationResource conflicts, idle GPUsJob scheduler with GPU requests (§4)
pip install without pinned versions"Works on my machine" failuresuv.lock or requirements.txt with exact pins
Training on full data without validation splitOverfitting, no early stopping signalAlways hold out validation + test splits

Pre-flight Checklist

Before running ML training:

  • [ ] Experiment tracker configured and logging params/metrics/artifacts
  • [ ] Training data versioned (DVC tag or Delta version recorded)
  • [ ] Data schema validated before training starts
  • [ ] Checkpointing configured (frequency, storage location)
  • [ ] GPU resources explicitly requested (type, count, memory)
  • [ ] Reproducibility: random seeds fixed, library versions pinned
  • [ ] Pipeline is idempotent — re-run produces same output
  • [ ] Model registry configured for promotion workflow (staging → production)