Skip to main content

Matrix jobs

Matrix jobs enable parallel execution of a job with different parameter combinations. You define variables with multiple values, and the system creates a separate job instance for each combination using the Cartesian product.

No include / exclude filtering: A matrix always expands to the full Cartesian product of its variables. There is no include or exclude filter — a key named include or exclude under matrix is treated as just another matrix variable (and its values become part of the combinations).

Schema​

See Schema Reference for the complete #Matrix definition.

Defining Matrices​

Basic Matrix​

A matrix with a single variable creates multiple parallel job instances:

jobs:
train-model:
name: Train with Different Algorithms
matrix:
algorithm: [xgboost, lightgbm, random_forest, neural_net]
runner: gpu-large
timeout: "2h"
steps:
- name: Train model
env:
ALGORITHM: ${{ .matrix.algorithm }}
run: |
python train.py --algorithm $ALGORITHM --data ./data/train.csv

This creates 4 job instances, one for each algorithm.

Multi-Variable Matrix​

Multiple variables create a Cartesian product of all combinations:

jobs:
hyperparameter-search:
name: Hyperparameter Tuning
matrix:
learning_rate: ["0.001", "0.01", "0.1"]
batch_size: ["32", "64", "128"]
runner: gpu-large
timeout: "3h"
steps:
- name: Train with hyperparameters
env:
LR: ${{ .matrix.learning_rate }}
BATCH: ${{ .matrix.batch_size }}
run: |
echo "Training with learning_rate=$LR and batch_size=$BATCH"
python train.py --lr $LR --batch-size $BATCH --epochs 100

- name: Upload model
upload:
path: models/model.pkl
destination: drive://models/lr-${{ .matrix.learning_rate }}-batch-${{ .matrix.batch_size }}/

This creates 9 job instances (3 learning rates × 3 batch sizes). Each instance gets a generated job ID of the form <job>-<value1>-<value2> and a display name of the form <name> (<value1>, <value2>). The matrix values are ordered by variable name (sorted alphabetically — here batch_size before learning_rate), and the variable names themselves are not part of the ID or display name:

Job IDDisplay name
hyperparameter-search-32-0.001Hyperparameter Tuning (32, 0.001)
hyperparameter-search-32-0.01Hyperparameter Tuning (32, 0.01)
hyperparameter-search-64-0.001Hyperparameter Tuning (64, 0.001)
......

(The display name uses the job's name if set, otherwise the job ID. When the job has no matrix, the ID and display name are just the job ID / name with no suffix.)

Matrix with Workflow Calls​

Matrices work with reusable workflows, passing matrix variables as inputs:

jobs:
evaluate-models:
name: Evaluate on Multiple Datasets
matrix:
model: [xgboost, lightgbm, random_forest]
dataset: [train, validation, test]
workflow:
name: model-evaluation
inputs:
model_type: ${{ .matrix.model }}
dataset_name: ${{ .matrix.dataset }}
metrics: "accuracy,f1,auc"

This creates 9 job instances (3 models × 3 datasets), each calling the model-evaluation workflow with different parameters.

Matrix Expressions​

Accessing Matrix Variables​

Matrix variables are accessed using the ${{ .matrix.variable_name }} expression syntax, consistent with other workflow expressions.

Format: ${{ .matrix.<variable_name> }}

Example:

jobs:
process:
matrix:
region: [us-east, eu-west, ap-south]
data_type: [transactions, events]
steps:
- name: Download data
download:
source: drive://data/${{ .matrix.region }}/${{ .matrix.data_type }}/
path: ./data/

- name: Process data
env:
REGION: ${{ .matrix.region }}
TYPE: ${{ .matrix.data_type }}
run: python process.py --region $REGION --type $TYPE

Job Expansion and Execution​

Parallel Execution​

All matrix job instances run in parallel by default. There is no automatic sequencing or max-parallel limiter—all combinations execute concurrently as soon as the job's dependencies are satisfied.

Dependency Handling​

When a job depends on a matrix job, it waits for ALL matrix instances to complete successfully.

Example:

jobs:
train:
matrix:
model: [xgboost, lightgbm, random_forest]
steps:
- run: python train.py --model ${{ .matrix.model }}

evaluate:
depends_on: [train] # Waits for all 3 training instances
steps:
- run: python evaluate_all.py --models-dir ./models/

Examples​

Example 1: Hyperparameter Optimization​

id: hyperparameter-optimization
name: Train Models with Different Hyperparameters

inputs:
dataset_bucket:
type: string
required: true
description: "Training dataset bucket UUID"

env:
SCRIPTS_REPO: "https://github.com/h2oai/ml-training"

jobs:
train-models:
name: Train with Hyperparameters
matrix:
algorithm: [xgboost, lightgbm, random_forest]
max_depth: ["5", "10", "15"]
runner: gpu-large
timeout: "3h"

steps:
- name: Download training data
download:
source: drive://${{ .inputs.dataset_bucket }}/train.csv
path: ./data/train.csv

- name: Clone training scripts
run: git clone --depth 1 $SCRIPTS_REPO scripts

- name: Train model
env:
ALGORITHM: ${{ .matrix.algorithm }}
MAX_DEPTH: ${{ .matrix.max_depth }}
run: |
echo "Training $ALGORITHM with max_depth=$MAX_DEPTH"
python scripts/train.py \
--algorithm $ALGORITHM \
--max-depth $MAX_DEPTH \
--data ./data/train.csv \
--output ./models/

- name: Upload trained model
upload:
path: ./models/
destination: drive://models/${{ .matrix.algorithm }}-depth${{ .matrix.max_depth }}/

- name: Upload metrics
upload:
path: metrics.json
destination: drive://metrics/${{ .matrix.algorithm }}-depth${{ .matrix.max_depth }}.json

This creates 9 parallel training runs (3 algorithms × 3 max_depth values).


Feedback