Ray Train Integration¶
Install¶
This guide assumes PyTorch and Ray Train are already installed. Add TraceML in the driver and training-worker environments:
pip install traceml-ai
Add TraceML to your training job¶
Use TraceMLTorchTrainer in place of Ray's TorchTrainer. Keep your existing
worker function, datasets, and Ray scaling configuration:
from traceml_ai.integrations.ray import TraceMLRayConfig, TraceMLTorchTrainer
trainer = TraceMLTorchTrainer(
train_loop_per_worker,
train_loop_config=train_loop_config,
scaling_config=scaling_config,
datasets=datasets,
traceml_config=TraceMLRayConfig(),
)
trainer.fit()
The adapter initializes TraceML in each worker before calling your function.
You do not need a separate traceml.init() call in that function.
Inside your existing PyTorch worker loop, mark each training step:
import traceml_ai as traceml
# Model, optimizer, loss function, and loader are created in this worker.
for batch in train_loader:
with traceml.trace_step(model):
x = batch["x"].to(device)
y = batch["y"].to(device).long()
optimizer.zero_grad(set_to_none=True)
loss = criterion(model(x), y)
loss.backward()
optimizer.step()
For Ray Data, wrap the iterator before entering the loop to measure input waiting:
from ray import train
train_ds = train.get_dataset_shard("train")
train_loader = traceml.wrap_dataloader_fetch(
train_ds.iter_torch_batches(batch_size=64, prefetch_batches=1)
)
A normal PyTorch DataLoader is timed automatically by the default worker initialization; do not wrap it again.
Run¶
Launch your configured Ray training script with Python:
python train.py
Ray launches the workers, and TraceMLTorchTrainer.fit() starts the TraceML
aggregator. This path does not require traceml run or traceml serve.
Keep your existing Ray cluster connection and ScalingConfig settings.
Read the result¶
When training finishes, TraceML prints a diagnosis of the likely bottleneck and saves the report for comparison or CI.
The timing breakdown shows available input waiting, forward, backward, and
optimizer measurements across workers. CUDA runs can also show host-to-device
and memory measurements. Reports are saved under the configured logs_dir
and session ID.
See How to Read Output for an example report and explanations.
How TraceML measures Ray training¶
Ray continues to manage worker placement, ranks, and distributed training. The adapter starts one TraceML aggregator actor and connects each worker's telemetry to it. Step measurements follow the boundaries you mark in the worker function:
Ray training worker TraceML measurement
──────────────────────────────────────────────────────────────
Wrapped Ray Data fetch Input Wait
↓
trace_step(model) Open the step capture
↓
Batch transfer → Forward H2D + forward timing
↓
Backward → Optimizer Backward + optimizer timing
↓
Exit trace_step Complete one reported step
In the loop above, one reported step contains one batch and one optimizer
update attempt. Ray does not automatically group gradient accumulation for
TraceML: the placement of trace_step() determines the reported boundary.
The Ray + Lightning setup below instead uses Lightning's callback to group
accumulating microbatches.
Input Wait measures how long the worker waits for the iterator, not total
preprocessing time inside Ray Data. Transfers must occur inside trace_step()
to be included in its H2D measurements.
Advanced options¶
Complete example¶
Minimal CPU example
import ray
from ray.train import ScalingConfig
from traceml_ai.integrations.ray import TraceMLRayConfig, TraceMLTorchTrainer
def train_loop_per_worker(config):
import torch
import torch.nn as nn
import torch.optim as optim
from ray import train
import traceml_ai as traceml
model = nn.Sequential(nn.Linear(32, 64), nn.ReLU(), nn.Linear(64, 4))
optimizer = optim.AdamW(model.parameters(), lr=1e-3)
criterion = nn.CrossEntropyLoss()
train_ds = train.get_dataset_shard("train")
train_loader = train_ds.iter_torch_batches(
batch_size=64,
prefetch_batches=1,
)
train_loader = traceml.wrap_dataloader_fetch(train_loader)
for step, batch in enumerate(train_loader):
if step >= config["steps"]:
break
with traceml.trace_step(model):
x = batch["x"]
y = batch["y"].long()
optimizer.zero_grad(set_to_none=True)
loss = criterion(model(x), y)
loss.backward()
optimizer.step()
ray.init()
train_dataset = ray.data.from_items(
[{"x": [0.0] * 32, "y": 0} for _ in range(4096)]
)
trainer = TraceMLTorchTrainer(
train_loop_per_worker,
train_loop_config={"steps": 10},
scaling_config=ScalingConfig(num_workers=4, use_gpu=False),
datasets={"train": train_dataset},
traceml_config=TraceMLRayConfig(mode="summary"),
)
trainer.fit()
Example scripts¶
Use --ray-address=auto when you already have a Ray cluster running, or omit
it for a local Ray run.
Minimal Ray Train example:
python examples/integrations/ray/torchtrainer_minimal.py \
--ray-address=auto \
--num-workers=2 \
--steps=100 \
--use-gpu
To make input timing visible in the minimal example:
python examples/integrations/ray/torchtrainer_minimal.py \
--ray-address=auto \
--num-workers=2 \
--steps=100 \
--use-gpu \
--input-delay-ms=100
Ray + Lightning¶
When combining Ray Train and PyTorch Lightning, add TraceMLCallback() to the
Lightning Trainer and keep wrapping Ray Data iterators with
traceml.wrap_dataloader_fetch(...). To capture Lightning H2D timing inside
Ray workers, initialize the worker patches selectively:
TraceMLRayConfig(
mode="summary",
init_mode="selective",
patch_dataloader=True,
patch_h2d=True,
)
The examples/integrations/ray/lightning_text_classifier.py demo also includes
--input-delay-ms / --input-delay-rank for input-straggler demos,
--delay-ms / --delay-rank for compute-straggler demos, and
--transfer-dim to make Lightning H2D timing visible.
--transfer-dim creates a reusable per-batch CPU tensor; it does not add a
full dataset-sized tensor.
Baseline Ray + Lightning run:
python examples/integrations/ray/lightning_text_classifier.py \
--ray-address=auto \
--num-workers=2 \
--max-steps=100 \
--use-gpu
Input-straggler demo:
python examples/integrations/ray/lightning_text_classifier.py \
--ray-address=auto \
--num-workers=2 \
--max-steps=100 \
--use-gpu \
--input-delay-rank=0 \
--input-delay-ms=200
Compute-straggler demo:
python examples/integrations/ray/lightning_text_classifier.py \
--ray-address=auto \
--num-workers=2 \
--max-steps=100 \
--use-gpu \
--delay-rank=0 \
--delay-ms=200
For CPU-only runs, remove --use-gpu.
Network Model¶
The aggregator runs as a normal Ray actor and binds a TCP server. By default it
binds 0.0.0.0 on port 0:
0.0.0.0lets workers on other Ray nodes connect to the actor node.- port
0lets the operating system choose a free port. - workers receive the actor's reachable node IP and chosen port through the wrapped trainer.
If your cluster requires a fixed open port, set it explicitly:
TraceMLRayConfig(port=29765)
Configuration¶
TraceMLRayConfig(
mode="summary",
profile="run",
init_mode="auto",
patch_dataloader=None,
patch_forward=None,
patch_backward=None,
patch_h2d=None,
logs_dir="./logs",
session_id="",
sampler_interval_sec=2.0,
history_retention="30m",
bind_host="0.0.0.0",
port=0,
)
The default mode="summary" is recommended for Ray because distributed worker
logs are noisy. Use mode="cli" only when you specifically want live terminal
rendering from the aggregator actor.
sampler_interval_sec defaults to 2.0 seconds. It controls worker sampling
and the aggregator actor's live UI refresh; incoming TCP telemetry is drained as
soon as it arrives.
history_retention accepts positive seconds or durations such as "30m",
"2h", or "1d". It defaults to 30 minutes of final-report analysis
history. Retention advances at completed, rank-aligned step boundaries and
does not create a rollup.
Migrating from summary_window_rows¶
The former row-count setting is no longer accepted. Configure the history duration instead:
# Before: no longer supported.
TraceMLRayConfig(summary_window_rows=1_000)
# Now: retain the duration needed for analysis.
TraceMLRayConfig(history_retention="30m")
A row count does not map to a fixed duration, so choose the duration explicitly
for your workload. The default is "30m"; it is not an automatic conversion
from 1,000 rows.
init_mode selects the mode passed to traceml.init() inside each Ray
worker. The Ray Data wrap_dataloader_fetch(...) pattern above works with
the default auto mode because Ray Data iterators are separate from PyTorch
DataLoader; wrapping a PyTorch DataLoader in the same mode raises to
prevent duplicate timing. Use init_mode="manual" only if your training
loop wraps dataloader, forward, backward, and optimizer timing explicitly.
Use init_mode="selective" with the patch_* options when you only want
some automatic patches.
Lifecycle¶
TraceMLTorchTrainer.fit() starts the aggregator actor, runs Ray Train, and
then stops the actor in a finally block. Each worker also stops its local
TraceML runtime in a finally block. Normal exceptions and keyboard
interrupts should therefore release TraceML resources. A hard SIGKILL cannot
run Python cleanup code in any framework.
If aggregator finalization fails, the actor records the failure in
aggregator/traceml_errors.log. Cleanup remains best effort at the Ray driver
boundary, so a TraceML shutdown failure does not replace the Ray Train result.
Limitations¶
- Explicit step setup. The adapter initializes worker instrumentation,
but custom loops still need
trace_step(). Ray Data iterators also needwrap_dataloader_fetch()for input timing. - Model preparation. Keep Ray's normal device placement and distributed model preparation in your worker function. The TraceML trainer adapter does not prepare or distribute the model for you.
- Network access. Workers must be able to reach the aggregator actor's TCP endpoint. Configure an accessible port when your cluster requires one.
- Validation evidence. See the support matrix for tested versions and hardware coverage.