lancedb.streaming.StreamingDataset is a PyTorch IterableDataset
that streams rows directly from a LanceDB table. Unlike the map-style Permutation dataset, it is
designed for large training runs where you cannot fit the dataset in memory and where the number of GPUs or
DataLoader workers may change between runs.
StreamingDataset provides two guarantees that are hard to get together with a normal PyTorch pipeline:
- Elastic determinism — for a fixed
(num_splits, shuffle_seed, epoch)the set of samples that forms each global training step is identical regardless ofworld_sizeornum_workers. Scale the cluster up or down between runs and the model sees the same data in the same order. - Resumability —
state_dict()/load_state_dict()capture a single topology-independent counter, so a checkpoint saved with 8 GPUs can resume correctly on 4 or 16 GPUs.
When to use it
UseStreamingDataset when any of the following are true:
- The training set does not fit in memory and you want to stream rows on demand.
- You need bit-exact reproducibility across cluster sizes (e.g. resuming a preempted job on a smaller node count).
- You want a filter (e.g.
"label = 'dog'") to be applied once at permutation-build time rather than re-evaluated on every fetch. - You are training on object storage and want prefetching to hide read latency.
Permutation API instead.
Quickstart
Python
transform callable if you need a different output format (see Transforms).
How it works
The table is partitioned intonum_splits fixed splits using a deterministic shuffle controlled by shuffle_seed
and epoch. Each rank is assigned a contiguous block of splits, and within a rank each DataLoader worker is
assigned a contiguous sub-block. Samples are yielded by round-robining over the assigned splits, one sample per
split per cycle.
Because the split structure is fixed, the set of samples that makes up each global step is independent of the
number of processes reading them — that is what makes the dataset elastic. It also means the resume state is a
single scalar (samples_consumed_per_split) rather than a per-rank or per-worker counter.
num_splits must be divisible by world_size, and when using DataLoader workers it must also be divisible by
world_size * num_workers. If the filtered row count is not evenly divisible by num_splits, up to
num_splits - 1 surplus rows are dropped per epoch to keep all splits the same length.Distributed training
Passrank and world_size explicitly so each process sees only its assigned splits:
Python
epoch at the top of each epoch to reshuffle. shuffle_seed stays constant across the run.
Resuming from a checkpoint
state_dict() returns a topology-independent snapshot. Save it alongside your model checkpoint and reload it into
a fresh dataset when resuming.
Python
load_state_dict raises ValueError if num_splits or shuffle_seed differ from the checkpoint, since a
different split structure or shuffle order makes mid-epoch resumption meaningless.
Filters as prefilters
Thefilter argument is applied once when the permutation is built. Only matching rows are stored in the
permutation table, so subsequent reads never touch non-matching rows on storage.
Python
Selecting columns
Passcolumns to read only the fields you need. Everything else is skipped on the storage read.
Python
Transforms
Pass atransform callable to decode, augment, or convert format before rows are yielded. The callable receives
one pyarrow.RecordBatch at a time and must return an iterable whose length equals the number of rows in the
batch.
Python
os.cpu_count(), so pure-Python transforms are limited by the
GIL. For heavy per-sample work, prefer transforms that release the GIL (NumPy, PyArrow, Torch, PIL decode) or
move the work into a Rust-backed pipeline.
Tuning I/O
Two parameters control the I/O pipeline:read_batch_size(default64) — number of rows fetched per storage request. Larger values amortise per-request overhead, which matters most on object storage where a single round-trip can cost ~100 ms.prefetch_batches(default4) — number of batches kept in flight per split. Higher values overlap storage latency with transform and training compute at the cost of more memory and threads.
Python
num_workers=0 often reaches full throughput because prefetching hides latency without
needing extra worker processes.
Multiple DataLoader workers
StreamingDataset supports num_workers > 0, but it is generally discouraged: PyTorch multiprocessing is
expensive to set up and duplicates connection state per worker. The built-in prefetcher usually makes it
unnecessary.
If you do use workers, num_splits must be divisible by world_size * num_workers, and you should use the
forkserver start method — LanceDB uses internal threads, so fork is unsafe.
Python