Advanced

CI/CD for ML

Build automated CI/CD pipelines for machine learning - from testing ML code to orchestrating end-to-end training and deployment pipelines.

CI/CD for ML Pipelines

Traditional CI/CD focuses on code. ML CI/CD must also handle data, models, and training pipelines as first-class artifacts:

  • Continuous Integration: Test code, validate data schemas, run quick model sanity checks on every commit.
  • Continuous Training: Automatically retrain models when new data arrives or performance degrades.
  • Continuous Delivery: Automatically deploy validated models to staging and production.

GitHub Actions for ML

YAML - .github/workflows/ml-pipeline.yml
name: ML Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: pip install -r requirements.txt

      - name: Run unit tests
        run: pytest tests/unit/ -v

      - name: Run data validation tests
        run: pytest tests/data/ -v

      - name: Run model tests
        run: pytest tests/model/ -v

  train:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: pip install -r requirements.txt

      - name: Train model
        run: python scripts/train.py --config configs/production.yaml
        env:
          MLFLOW_TRACKING_URI: ${{ secrets.MLFLOW_TRACKING_URI }}

      - name: Validate model
        run: python scripts/validate_model.py --min-accuracy 0.85

      - name: Register model
        run: python scripts/register_model.py
        env:
          MLFLOW_TRACKING_URI: ${{ secrets.MLFLOW_TRACKING_URI }}

Testing ML Code

ML code requires multiple types of tests:

Unit Tests

Python - Unit tests for ML code
import pytest
import numpy as np
from src.features import compute_features
from src.preprocessing import clean_data

class TestFeatureEngineering:
    def test_compute_features_shape(self):
        """Features should have expected number of columns."""
        raw_data = pd.DataFrame({"amount": [100, 200], "category": ["A", "B"]})
        features = compute_features(raw_data)
        assert features.shape[1] == 10  # Expected feature count

    def test_compute_features_no_nulls(self):
        """Computed features should have no null values."""
        raw_data = pd.DataFrame({"amount": [100, None, 200], "category": ["A", "B", "C"]})
        features = compute_features(clean_data(raw_data))
        assert features.isnull().sum().sum() == 0

    def test_feature_values_in_range(self):
        """Normalized features should be between 0 and 1."""
        raw_data = pd.DataFrame({"amount": [100, 200, 300]})
        features = compute_features(raw_data)
        assert features["amount_normalized"].between(0, 1).all()

Integration Tests

Python - Integration tests for ML pipeline
class TestTrainingPipeline:
    def test_end_to_end_pipeline(self, sample_dataset):
        """Full pipeline should run without errors."""
        model = train_pipeline(sample_dataset, config="test")
        predictions = model.predict(sample_dataset.drop("target", axis=1))
        assert len(predictions) == len(sample_dataset)

    def test_model_serialization(self, trained_model):
        """Model should be serializable and loadable."""
        save_model(trained_model, "test_model.pkl")
        loaded_model = load_model("test_model.pkl")
        assert np.allclose(
            trained_model.predict(X_test),
            loaded_model.predict(X_test)
        )

Model Tests

Python - Model quality tests
class TestModelQuality:
    def test_minimum_accuracy(self, trained_model, test_data):
        """Model must meet minimum accuracy threshold."""
        accuracy = accuracy_score(test_data.y, trained_model.predict(test_data.X))
        assert accuracy >= 0.85, f"Accuracy {accuracy:.4f} below threshold 0.85"

    def test_no_class_bias(self, trained_model, test_data):
        """Model should perform reasonably across all classes."""
        report = classification_report(test_data.y, trained_model.predict(test_data.X), output_dict=True)
        for cls, metrics in report.items():
            if cls in ["accuracy", "macro avg", "weighted avg"]:
                continue
            assert metrics["f1-score"] >= 0.5, f"Class {cls} F1 too low: {metrics['f1-score']}"

    def test_prediction_latency(self, trained_model, single_sample):
        """Prediction latency must be under 100ms."""
        import time
        start = time.time()
        trained_model.predict(single_sample)
        latency = (time.time() - start) * 1000
        assert latency < 100, f"Latency {latency:.1f}ms exceeds 100ms"

Automated Model Validation

Before promoting a model to production, validate it automatically:

  1. Performance gate

    New model must meet or exceed the current production model's metrics on a holdout test set.

  2. Fairness check

    Verify model performance across demographic groups. Flag disparities above threshold.

  3. Latency test

    Ensure inference time meets SLA requirements under load.

  4. Data compatibility

    Verify the new model handles the current production data schema correctly.

  5. Shadow deployment

    Run the new model alongside production, comparing outputs before switching traffic.

Pipeline Orchestration Platforms

PlatformProviderKey Features
Kubeflow PipelinesGoogle (OSS)Kubernetes-native, reusable components, experiment tracking
Vertex AI PipelinesGoogle CloudManaged Kubeflow, AutoML, integrated monitoring
SageMaker PipelinesAWSManaged, model registry, bias detection, lineage
Azure ML PipelinesMicrosoftManaged, designer UI, responsible AI dashboard
ZenMLOSSFramework-agnostic, stack components, reproducibility

Infrastructure as Code for ML

HCL - Terraform for ML infrastructure
# Define ML training infrastructure
resource "aws_sagemaker_notebook_instance" "ml_notebook" {
  name          = "ml-team-notebook"
  instance_type = "ml.t3.medium"
  role_arn      = aws_iam_role.sagemaker_role.arn
}

resource "aws_s3_bucket" "model_artifacts" {
  bucket = "ml-model-artifacts-prod"

  versioning {
    enabled = true
  }

  lifecycle_rule {
    enabled = true
    transition {
      days          = 90
      storage_class = "GLACIER"
    }
  }
}

resource "aws_ecr_repository" "ml_model" {
  name                 = "ml-model-serving"
  image_tag_mutability = "IMMUTABLE"

  image_scanning_configuration {
    scan_on_push = true
  }
}
💡
Key principle: Treat your ML infrastructure the same as your application infrastructure. Version it, review it, test it, and deploy it through the same CI/CD pipeline.

Ready to Go Deeper?

Live instructor-led courses from our partners. Affiliate disclosure.