CI/CD for ML: GitHub Actions, Jenkins, and Continuous Training
1608 words
8 min read
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
# CI/CD for ML: GitHub Actions, Jenkins, and Continuous Training ## 🎯 Learning Objectives - Design CI/CD pipelines for ML projects - Implement automated model training and testing with GitHub Actions - Set up Continuous Training (CT) pipelines - Validate models before production deployment - Understand the differen...

CI/CD for ML: GitHub Actions, Jenkins, and Continuous Training
🎯 Learning Objectives
- Design CI/CD pipelines for ML projects
- Implement automated model training and testing with GitHub Actions
- Set up Continuous Training (CT) pipelines
- Validate models before production deployment
- Understand the differences between software CI/CD and ML CI/CD
📋 Prerequisites
- Git/GitHub basics: Repos, branches, PRs
- ML Pipeline concepts: Training, evaluation, deployment stages
- Docker basics (Week 5): Container images
1. 📖 Core Content
1.1 Intuition: Why CI/CD for ML?
In software engineering, CI/CD ensures every code change is automatically tested and deployed. For ML, it's more complex because we have two artifacts: code AND model.
(Diagram)
Key differences from software CI/CD:
- Data changes can trigger retraining (not just code changes)
- Model validation is more complex (not just pass/fail)
- Two artifacts to version (code + model weights)
- Training is expensive (hours vs minutes for software tests)
1.2 ML CI/CD Pipeline Stages
(Diagram)
1.3 GitHub Actions for ML
1.3.1 Basic Training Pipeline
yamlname: ML Pipeline on: push: branches: [main] pull_request: branches: [main] jobs: train: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Python uses: actions/setup-python@v4 with: python-version: '3.9' - name: Install Dependencies run: | pip install -r requirements.txt pip install pytest pandas scikit-learn - name: Run Tests run: pytest tests/ -v - name: Train Model run: python src/train.py - name: Evaluate Model run: python src/evaluate.py - name: Upload Model Artifact uses: actions/upload-artifact@v3 with: name: model path: models/model.pkl
1.3.2 Continuous Training with Scheduled Trigger
yamlname: Continuous Training on: schedule: - cron: '0 6 * * 1' # Every Monday at 6 AM workflow_dispatch: # Manual trigger jobs: retrain: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Fetch Latest Data run: python scripts/fetch_latest_data.py - name: Train Model run: python src/train.py --data latest - name: Evaluate vs Production run: python src/compare_models.py \ --new models/new_model.pkl \ --prod models/prod_model.pkl - name: Promote if Better run: | if python src/check_improvement.py; then echo "Model improved! Promoting to production." python src/promote_to_prod.py else echo "No significant improvement." fi
1.4 Model Validation Gates
Before promoting a model to production, it must pass validation gates:
| Gate | Test | Threshold | Action if Failed |
|---|---|---|---|
| 1. Data validation | Check schema, distributions | Feature distributions within drift threshold | Alert data team |
| 2. Functional tests | Model runs without errors | API returns 200 | Block deployment |
| 3. Performance | Metrics on held-out test set | Accuracy > baseline by 1% | Reject model |
| 4. Fairness | Performance across subgroups | Demographic parity ratio > 0.8 | Review & document |
| 5. Robustness | Adversarial/edge case testing | Within 5% of clean accuracy | Add robustness training |
| 6. Shadow test | Online evaluation with live traffic | Error rate below threshold | Rollback shadow |
Worked Example 1: Model Evaluation Gate
python# runnable def validate_model(new_model, baseline_model, test_data): """Validate new model against baseline.""" results = {} # Gate 1: Test set performance new_score = evaluate(new_model, test_data) baseline_score = evaluate(baseline_model, test_data) results['accuracy_gain'] = new_score - baseline_score results['accuracy_gate'] = results['accuracy_gain'] >= 0.01 # 1% improvement # Gate 2: Check for performance degradation on subsets for subgroup in ['low_income', 'high_income', 'young', 'old']: sub_data = test_data.filter(pl.col('group') == subgroup) new_sub_score = evaluate(new_model, sub_data) baseline_sub_score = evaluate(baseline_model, sub_data) results[f'fairness_{subgroup}'] = (new_sub_score / baseline_sub_score) > 0.95 # Gate 3: Robustness to missing values corrupted = test_data.with_columns(pl.col('feature_1').fill_null(0)) robust_score = evaluate(new_model, corrupted) clean_score = evaluate(new_model, test_data) results['robustness'] = (robust_score / clean_score) > 0.9 # Overall gate all_gates_passed = ( results['accuracy_gate'] and all(results[f'fairness_{g}'] for g in ['low_income', 'high_income', 'young', 'old']) and results['robustness'] ) return all_gates_passed, results
1.5 Continuous Training (CT)
Continuous Training automatically retrains models as new data arrives. This is needed because model performance degrades over time (concept drift).
(Diagram)
1.5.1 When to Retrain?
| Trigger | Approach | Best For |
|---|---|---|
| Fixed schedule | Retrain weekly/monthly | Stable environments |
| Data volume | Retrain after N new samples | High-volume pipelines |
| Drift detection | Retrain when metric drops | Dynamic environments |
| On-demand | Manual trigger | Controlled deployments |
1.6 CI/CD Tools Comparison
| Tool | Setup | ML-Specific Features | Pricing |
|---|---|---|---|
| GitHub Actions | Easy | Limited (general CI/CD) | Free for public repos |
| GitLab CI | Easy | Limited | Free tier available |
| Jenkins | Complex | Very flexible | Free |
| CircleCI | Medium | Limited | Paid tiers |
| Kubeflow | Complex | Full ML pipeline support | Open source |
| MLflow | Medium | Model registry, evaluation | Free + managed |
1.7 Edge Cases & Gotchas
- Training timeouts: Long training runs may exceed CI limits (GitHub Actions: 6 hours). Use self-hosted runners or separate training cluster.
- Data access: CI/CD runners may not have access to production databases. Use sampled data for tests, full data for training.
- GPU availability: Standard CI runners don't have GPUs. Use cloud runners (GitHub Actions + AWS/GCP) or self-hosted GPU runners.
- Model artifact size: Large models (5+ GB) aren't suitable for artifact storage. Use cloud storage (S3, GCS) with registry pointers.
- Credential management: Store API keys and secrets in GitHub Secrets / Jenkins Credentials, not in code.
1.8 Why This Matters
CI/CD for ML is essential for:
- Team collaboration: Automated checks before merging code
- Reproducibility: Every training run is logged and versioned
- Deployment velocity: Push models to production with confidence
- Compliance: Audit trail of model changes for regulated industries Without ML CI/CD, teams face "works on my machine" problems, manual deployment errors, and untracked model changes.
2. 📐 Key Formulas / Concepts
| Concept | Description | Tools |
|---|---|---|
| CI/CD pipeline | Automated build, test, deploy | GitHub Actions, Jenkins |
| Model validation | Performance, fairness, robustness gates | MLflow, custom scripts |
| Continuous Training | Automated retraining on schedule/drift | KFP, Airflow |
| Model registry | Versioned storage of model artifacts | MLflow, DVC, S3 |
| Shadow deployment | New model alongside existing | Kubernetes, Istio |
3. ⚠️ Common Pitfalls
Pitfall 1: Treating ML CI/CD Like Software CI/CD
Mistake: Same pipeline for code changes as for retraining runs.
Why: Software CI/CD runs in minutes; ML training can run for hours. Software tests are deterministic; ML evaluation has statistical noise.
Correct approach: Separate pipelines for code CI (fast, runs on every PR) and model training CT (slow, runs on schedule or trigger).
Pitfall 2: Not Versioning Data Alongside Code and Model
Mistake: Only versioning code (Git) and model (registry) but not the training data.
Why: Without data versioning, you can't reproduce a model if the original data changes.
Correct approach: Use DVC for data versioning. Every model in the registry references: commit hash (code) + data URI (data) + model artifact.
Pitfall 3: Deploying Models Without Shadow Testing
Mistake: Promoting a model to production based only on offline evaluation.
Why: Offline metrics don't always correlate with online performance. Data distribution, user behavior, or latency patterns differ between test and production.
Correct approach: Deploy the new model to a shadow (dark) environment first. Route production traffic to the existing model AND the shadow model. Compare online metrics without impacting users.
4. 📝 Practice Questions
Q1: Design a GitHub Actions pipeline that: (a) runs unit tests on every PR, (b) trains a model on PR merge to main, (c) deploys to production only if model improves.yamlname: ML CI/CD on: pull_request: branches: [main] push: branches: [main] jobs: # Job 1: Fast checks on every PR test: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: { python-version: '3.9' } - run: pip install -r requirements.txt - run: pytest tests/ -v --timeout=120 - run: python scripts/validate_data.py # Job 2: Train on push to main train: if: github.event_name == 'push' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 - run: pip install -r requirements.txt - run: python src/train.py --output models/candidate.pkl # Compare with production - run: python src/compare.py --candidate models/candidate.pkl --prod prod/model.pkl # Deploy if better - name: Deploy Candidate if: steps.compare.outputs.improved == 'true' run: python scripts/deploy.py models/candidate.pklQ2: Your scheduled retraining pipeline ran, but the new model's accuracy dropped 5% on the validation set. What should happen?The pipeline should:
- Log the failure: Record the new model's metrics in MLflow alongside the decreased accuracy
- Alert the team: Send notification (Slack/email) with summary: "Model v2.1 accuracy dropped from 92% to 87%"
- Preserve current production model: Do NOT deploy the worse model
- Investigate root cause: Check data drift (feature distributions), concept drift (relationship shift), data quality issues (missing values, labeling errors)
- Trigger data pipeline: If data quality issue is detected, trigger data validation and correction
- Option to retrain with old data: If data is corrupted, retrain on the last good dataset version
The automated checkif accuracy_drop > threshold: block_deployment = Trueprevents automatic degradation. Q3: Compare the CI/CD costs for a model that trains in 30 minutes (CPU) vs 6 hours (GPU). How would each fit into typical CI/CD budgets?CPU model (30 min):
- GitHub Actions: 30 min × 0.008/min(standard)=0.24 per run
- Daily retrain: ~$7.20/month
- Weekly retrain: ~$1.00/month
Fits comfortably in any CI/CD budget. Can run multiple times per day.GPU model (6 hours):
- GitHub Actions: 360 min × 0.08/min(GPUrunner)=28.80 per run
- Daily retrain: ~$864/month
- Weekly retrain: ~$115/month
Too expensive for daily GitHub Actions GPU runs. Alternatives:
- Self-hosted GPU runner: One-time cost, lower per-run cost
- Separate training cluster (AWS Batch, GCP AI Platform): Pay per instance, more flexible
- Reduce frequency: Weekly retrain instead of daily
- Hybrid: Train on cheap preemptible/spot instances, only use GPU CI for validation
For GPU training-heavy pipelines, most teams use a dedicated compute cluster rather than CI-integrated runners.
5. 🔗 Cross-References
- Previous: Orchestration: K8s & Kubeflow (Week 6) — Pipeline orchestration
- Next: Model Serving (Week 8) — Deploying validated models
- Related: Experiment Tracking (Week 2) — Logging runs during CI
- External: GitHub Actions documentation — Practical CI/CD examples Join Discord PreviousOrchestration: K8s & KubeflowNextModel Serving