Sharded Jobs¶
Sharding allows you to run the same job across multiple parallel instances, with each shard processing a portion of the data. This is ideal for embarrassingly parallel workloads and data parallelism.
Basic Sharding¶
Use the -N flag to specify the number of shards:
Each shard automatically receives environment variables identifying its position:
import os
shard_id = int(os.environ['VELDA_SHARD_ID']) # 0, 1, 2, or 3
total_shards = int(os.environ['VELDA_TOTAL_SHARDS']) # 4
# Process only this shard's portion of data
for i in range(shard_id, len(data), total_shards):
process(data[i])
Use Cases¶
Data Parallelism¶
Process large datasets by dividing them across shards:
# Process 1M records using 8 shards
vbatch -N 8 --name process-records python process_records.py --total 1000000
Each shard processes ⅛th of the data:
import os
shard_id = int(os.environ['VELDA_SHARD_ID'])
total_shards = int(os.environ['VELDA_TOTAL_SHARDS'])
total_records = 1_000_000
# Calculate this shard's range
records_per_shard = total_records // total_shards
start = shard_id * records_per_shard
end = start + records_per_shard if shard_id < total_shards - 1 else total_records
print(f"Shard {shard_id} processing records {start} to {end}")
process_records(start, end)
Distributed Training¶
Run data-parallel training across multiple GPUs:
# Train on 4 GPUs with data parallelism
vbatch -N 4 -P gpu-1 --name distributed-training python train_distributed.py
Example using PyTorch:
import os
import torch
import torch.distributed as dist
# Use VELDA_SHARD_ID as the rank
rank = int(os.environ['VELDA_SHARD_ID'])
world_size = int(os.environ['VELDA_TOTAL_SHARDS'])
# Initialize distributed training
dist.init_process_group(backend='nccl', rank=rank, world_size=world_size)
# Train with DistributedDataParallel
model = torch.nn.parallel.DistributedDataParallel(model)
train(model)
Parameter Sweeps¶
Search hyperparameter space in parallel:
# Each shard tests different hyperparameters
vbatch -N 10 --name hyperparam-sweep python hyperparam_search.py
import os
shard_id = int(os.environ['VELDA_SHARD_ID'])
total_shards = int(os.environ['VELDA_TOTAL_SHARDS'])
# Define hyperparameter space
learning_rates = [1e-5, 1e-4, 1e-3, 1e-2, 1e-1]
batch_sizes = [16, 32, 64, 128]
# Create all combinations
configs = [(lr, bs) for lr in learning_rates for bs in batch_sizes]
# Each shard tests a subset of configurations
for i in range(shard_id, len(configs), total_shards):
lr, batch_size = configs[i]
print(f"Testing lr={lr}, batch_size={batch_size}")
train_and_evaluate(lr, batch_size)
File Processing¶
Process multiple files in parallel:
# List files and process with sharding
vbatch -N 8 --name process-files python process_files.py /data/*.csv
import os
import sys
import glob
shard_id = int(os.environ['VELDA_SHARD_ID'])
total_shards = int(os.environ['VELDA_TOTAL_SHARDS'])
# Get all files
files = sorted(glob.glob('/data/*.csv'))
# Process only this shard's files
for i in range(shard_id, len(files), total_shards):
print(f"Processing {files[i]}")
process_file(files[i])
Sharded Testing (pytest)¶
You can run your test suite in parallel across shards by having pytest select a subset of collected tests per shard. A simple and reliable approach is to partition the collected items in conftest.py using the VELDA_SHARD_ID and VELDA_TOTAL_SHARDS environment variables.
conftest.py:
import os
def pytest_collection_modifyitems(config, items):
shard_id = int(os.environ.get('VELDA_SHARD_ID', '0'))
total_shards = int(os.environ.get('VELDA_TOTAL_SHARDS', '1'))
# Keep only items that belong to this shard (round-robin by index)
selected = [item for idx, item in enumerate(items) if idx % total_shards == shard_id]
items[:] = selected
Run the test suite across 4 shards with Velda:
Or run a single shard locally (for debugging):
Homogeneous Shards¶
Homogeneous shards run identical worker processes that pull work from a shared job queue. The queue is responsible for distributing work; each worker is interchangeable.
Start N workers (job queue)¶
Example: start N identical Python workers that pop jobs from Redis and process them.
worker.py:
import os
import time
import json
import redis
REDIS_URL = os.environ.get('REDIS_URL', 'redis://localhost:6379/0')
r = redis.from_url(REDIS_URL)
def process_job(job):
data = json.loads(job)
# do the real work
print('processing', data)
if __name__ == '__main__':
while True:
job = r.lpop('jobs')
if not job:
time.sleep(1)
continue
try:
process_job(job)
except Exception:
# consider pushing failed jobs to a dead-letter queue
raise
Start 8 workers with Velda:
Each worker is identical and will claim the next job from the shared queue; this scales horizontally by increasing -N.