Online Data Streaming#
This page documents the online data streaming pipeline used for live training from simulation. The core pieces are:
OnlineDataEngine: a process-safe shared buffer that stores trajectories coming from live simulation workers.
OnlineDataset: a PyTorch
IterableDatasetthat samples trajectory chunks from the engine in either item mode or batch mode.ChunkSizeSampler: an interface for drawing dynamic chunk sizes per iteration step.
These components live under embodichain/data_pipeline/ and are designed to work with standard DataLoader patterns.
OnlineDataEngine#
Module: embodichain/data_pipeline/engine/data.py
OnlineDataEngine manages an in-memory, shared buffer for streaming trajectory data. A typical usage pattern is:
Build and start the engine with
OnlineDataEngineCfg.Run simulation workers that continually push new experience into the engine.
Train by sampling trajectory chunks from the engine via
OnlineDataset.
Key ideas:
Shared buffer: the simulation producer and training consumers can read/write concurrently.
GPU-friendly: buffer is designed for efficient sampling and minimal copying.
Chunked sampling: training samples fixed-length or dynamically sized chunks.
Transactional rows: the current write window is locked until a complete, successful episode replaces it. Failed generation attempts never become sampleable.
Variable lengths: every frame has a
validflag. Sampling constructs windows only from real frames and never reads zero padding or a stale tail.Episode-uniform sampling: an eligible episode row is selected uniformly, then a valid start offset is selected within that row. Longer episodes do not receive extra probability merely because they contain more windows.
Explicit lifecycle: the engine progresses through
CREATED,STARTING,READY, and a terminalFAILEDorSTOPPEDstate. Sampling is allowed only inREADY, and a stopped or failed instance cannot be restarted.Fail-fast errors: initial-fill timeouts, rollout failures, hard worker exits, and shutdown/recorder failures are raised to the owner. The same stable error snapshot is visible to every forked or spawned data consumer.
Each row stores one complete task episode. A row may contain multiple semantic segments. In addition to observations, actions, and rewards, the shared buffer contains:
valid, episode_step, segment_id, segment_step,
segment_start, segment_end, terminated, truncated
Tasks use the same create_demo_segments() protocol as run-env; legacy
create_demo_action_list() tasks are treated as one segment. The worker checks
termination after every action and retries a failed episode up to
max_generation_attempts before reporting an error.
start() blocks until the initial fill is complete and is bounded by
initialization_timeout. If the worker exits or generation fails, start()
raises the worker error instead of waiting indefinitely. A later worker failure
is reported on the next sample_batch() call rather than silently serving a
permanently stale buffer.
Minimal setup#
from embodichain.data_pipeline.engine import OnlineDataEngine, OnlineDataEngineCfg
cfg = OnlineDataEngineCfg(
buffer_size=2, # trajectories kept in the shared buffer
state_dim=6, # example state dimension
gym_config=your_gym_cfg, # parsed gym config for the task
initialization_timeout=300, # maximum seconds for the initial fill
)
engine = OnlineDataEngine(cfg)
engine.start()
Shutdown#
Prefer a context manager so cleanup runs on both success and failure:
with OnlineDataEngine(cfg) as engine:
batch = engine.sample_batch(batch_size=32, chunk_size=64)
train_step(batch)
For an explicitly managed lifecycle, call stop() in the process that created
the engine:
engine = OnlineDataEngine(cfg)
engine.start()
try:
train(engine)
finally:
engine.stop()
stop() is idempotent after a successful cleanup. It waits for the producer
and raises any failure reported while the worker closes its environment or
flushes committed data. If graceful shutdown times out and the worker requires
terminate() or kill(), durability cannot be confirmed: the engine remains
FAILED and stop() raises instead of reporting a false STOPPED state. When
a with body and cleanup both fail, the body exception remains primary and the
cleanup failure is attached as a note.
Start the engine before constructing multiprocessing DataLoader workers.
Forked and spawned copies may call sample_batch(), but only the original
owner process may call start() or stop(); consumer destructors never signal
the shared producer.
OnlineDataset#
Module: embodichain/data_pipeline/datasets/online_data.py
OnlineDataset wraps a live OnlineDataEngine and exposes a PyTorch IterableDataset. It supports two modes:
Item mode (default)#
Create the dataset with
batch_size=None(default).Each iteration yields a single
TensorDictof shape[chunk_size, ...].Use
DataLoader(dataset, batch_size=B)to let the DataLoader stack items into batches.
from torch.utils.data import DataLoader
from embodichain.data_pipeline.datasets import OnlineDataset
dataset = OnlineDataset(engine, chunk_size=64)
loader = DataLoader(
dataset,
batch_size=32,
collate_fn=OnlineDataset.collate_fn,
)
for batch in loader:
# batch shape: [32, 64, ...]
train_step(batch)
Batch mode#
Create the dataset with
batch_size=N.Each iteration yields a pre-batched
TensorDictof shape[N, chunk_size, ...].Use
DataLoader(dataset, batch_size=None)to bypass auto-collation.
dataset = OnlineDataset(engine, chunk_size=64, batch_size=32)
loader = DataLoader(
dataset,
batch_size=None,
collate_fn=OnlineDataset.passthrough_collate_fn,
)
for batch in loader:
# batch shape: [32, 64, ...]
train_step(batch)
Dynamic chunk sizes#
Pass a ChunkSizeSampler instead of an int to chunk_size to sample a new length each iteration step.
from embodichain.data_pipeline.datasets.sampler import UniformChunkSampler
sampler = UniformChunkSampler(low=16, high=64)
dataset = OnlineDataset(engine, chunk_size=sampler)
In batch mode, the sampler is called once per step so all trajectories in the batch share the same chunk length.
Segment-aware sampling#
Set sampling_mode according to the training objective:
# Chunks may span adjacent subtasks (default).
episode_dataset = OnlineDataset(engine, chunk_size=64, sampling_mode="episode")
# Every chunk stays inside one pick/place segment.
segment_dataset = OnlineDataset(engine, chunk_size=32, sampling_mode="segment")
# Every chunk contains an internal transition between two segments.
boundary_dataset = OnlineDataset(engine, chunk_size=32, sampling_mode="boundary")
All three modes still require every sampled frame to be valid. boundary
requires a chunk size of at least two and raises a clear error when no internal
boundary can satisfy the requested length.
ChunkSizeSampler#
Module: embodichain/data_pipeline/datasets/sampler.py
ChunkSizeSampler is a small interface that returns a positive integer chunk size each time it is called.
Built-in samplers:
UniformChunkSampler(low, high): discrete uniform over[low, high].GMMChunkSampler(means, stds, weights, low, high): Gaussian mixture with optional bounds.
Example (GMM):
from embodichain.data_pipeline.datasets.sampler import GMMChunkSampler
sampler = GMMChunkSampler(
means=[16.0, 64.0],
stds=[4.0, 8.0],
weights=[0.6, 0.4],
low=8,
high=96,
)
End-to-end demo#
A runnable example that wires everything together is provided in:
examples/data_pipeline/online_dataset_demo.py
It shows item mode, batch mode, and dynamic chunk sizes. Run it with:
python examples/data_pipeline/online_dataset_demo.py
See Also#
RL Architecture — RL training pipeline
Expert Data Generation Tutorial — Generating offline datasets