Motivation
Replay samples currently expose physical storage indices. With a round-robin writer, an index can
be overwritten while the learner is computing a loss. A later priority update can therefore apply
the TD error of the sampled record to a different, newer record that now occupies the same slot.
The replay lock makes sampling and individual writes atomic, but it does not protect an index for
the full sample -> learner step -> priority update interval. Holding a storage lease for that whole
interval would prevent useful writer progress, so delayed updates need a way to detect slot reuse.
Proposal
Add an optional generation stamp to replay indices.
- A compatible writer increments the generation of every physical slot when that slot is reused.
- Sampling returns both the physical index and the generation that was observed for it.
- A conditional priority-update API compares both values while holding the appropriate replay/write
lock and updates only entries that are still live.
- The operation returns which updates succeeded, allowing callers to report or ignore stale
learner results.
For TensorDict replay buffers, the metadata could be represented as two tensor keys so it remains
easy to batch, reshape, move and serialize:
sample["index"]
sample["index_generation"]
An initial API could look like:
result = rb.update_priority_if_present(
index=sample["index"],
generation=sample["index_generation"],
priority=sample["td_error"],
)
result.updated_mask
result.num_updated
result.num_stale
The exact public representation can differ, but the index and generation must travel together
through sampling, collation and multidimensional-storage reshaping.
Scope
Start with round-robin tensor storage and prioritized replay. The design should leave room for
other writers and storages to advertise generation support without requiring every storage backend
to implement it immediately.
Priority remains sampler metadata. This issue does not introduce general mutation of stored replay
fields; that can build on the same stamped-index mechanism separately.
Backward compatibility
- Existing raw
index metadata and update_priority() remain supported.
- Existing calls retain their current behavior.
- Safe delayed updates use the new conditional method.
- Buffers whose writer/storage does not support generations should fail clearly at construction or
when the conditional API is requested, rather than silently degrading to raw-index behavior.
Acceptance criteria
- Round-robin writes maintain a generation tensor with the same physical index space as storage.
- Samples return generation stamps matching their records.
- An update succeeds when a slot has not been reused.
- An update is skipped when the same physical index has been overwritten.
- Mixed batches report partial success correctly.
- The generation check and priority update are atomic with respect to writers.
- Multidimensional storage indices are supported.
- Generation state survives replay
state_dict and dump/load round trips.
- Shared/multiprocess behavior is either supported and tested or rejected explicitly.
- Tests reproduce the delayed-learner wraparound race deterministically.
Open questions
- Should generation metadata be enabled by default for round-robin tensor storage or opt-in?
- Should the public object remain two tensors or become an opaque structured replay-index type?
- What generation dtype and overflow behavior should be guaranteed?
- Should all conditional update APIs share one result type?
cc @theap06
Motivation
Replay samples currently expose physical storage indices. With a round-robin writer, an index can
be overwritten while the learner is computing a loss. A later priority update can therefore apply
the TD error of the sampled record to a different, newer record that now occupies the same slot.
The replay lock makes sampling and individual writes atomic, but it does not protect an index for
the full sample -> learner step -> priority update interval. Holding a storage lease for that whole
interval would prevent useful writer progress, so delayed updates need a way to detect slot reuse.
Proposal
Add an optional generation stamp to replay indices.
lock and updates only entries that are still live.
learner results.
For TensorDict replay buffers, the metadata could be represented as two tensor keys so it remains
easy to batch, reshape, move and serialize:
An initial API could look like:
The exact public representation can differ, but the index and generation must travel together
through sampling, collation and multidimensional-storage reshaping.
Scope
Start with round-robin tensor storage and prioritized replay. The design should leave room for
other writers and storages to advertise generation support without requiring every storage backend
to implement it immediately.
Priority remains sampler metadata. This issue does not introduce general mutation of stored replay
fields; that can build on the same stamped-index mechanism separately.
Backward compatibility
indexmetadata andupdate_priority()remain supported.when the conditional API is requested, rather than silently degrading to raw-index behavior.
Acceptance criteria
state_dictand dump/load round trips.Open questions
cc @theap06