Skip to content

[Feature] Add generation-stamped replay buffer slots - #4042

Closed
theap06 wants to merge 2 commits into
pytorch:mainfrom
theap06:feature/rb-slot-generations
Closed

[Feature] Add generation-stamped replay buffer slots#4042
theap06 wants to merge 2 commits into
pytorch:mainfrom
theap06:feature/rb-slot-generations

Conversation

@theap06

@theap06 theap06 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Description

First step of the conditional replay-update RFC (#4040), implementing the dependency described in #4041: replay slots get a reuse counter, and samples report which version of a slot they came from.

Round-robin writers now stamp each storage slot with an int64 generation: the first write of a slot has generation 0 and every round-robin reuse increments it, so a captured (index, generation) pair identifies one specific record and becomes detectably stale once the slot is recycled or the buffer is emptied. Nothing acts on the stamps yet; the conditional write-back API that consumes them is submitted separately.

  • writer.generations_of(index) is the one new public symbol (int64, dim-0 reduced for multidimensional storages). Writers that do not track reuse (ImmutableDatasetWriter, TensorDictMaxValueWriter, ensembles) report -1 and are exempt from the sampling surface via a tracks_generations flag.
  • sample(return_info=True) gains an "index_generation" info entry aligned with "index", read under the same lock acquisition that produced the sample so the handle is coherent; TensorDict buffers surface it as a sample key through the existing info-copy path.
  • empty() invalidates all outstanding handles (generations bump, never reset, and stay monotonic across the buffer lifetime).
  • Persistence: generations travel through state_dict/load_state_dict and dumps/loads (memmap sidecar next to metadata.json); checkpoints from before this change still load.
  • Lazily-initialized storages report a sentinel capacity (2**63-1); the generation tensor detects this and grows dynamically from the highest written slot instead of allocating by capacity.

Deliberate semantics, stated rather than implied:

  • write_at and raw __setitem__/set_at_ patch records in place and do not advance generations (positional patching preserves identity; a follow-up could stamp __setitem__ if desired).
  • A single extend that wraps the storage and writes a slot twice advances its generation once.
  • Cross-process visibility of the stamps for share()-d buffers is deferred to a follow-up.

Testing

TestSlotGenerations (writer level: counting, add/extend wraparound, empty semantics, state_dict/dumps round-trips, legacy checkpoints) and TestSampleGenerationInfo (info entry, TensorDict sample key, staleness detectable after wraparound) — written spec-first before the implementation. Four existing tests that assert over all sample keys were adapted to exclude the new key. Full test/rb/ suite passes (4205 tests).

A wraparound-heavy extend benchmark (test_rb_extend_generation_stamping) covers the hot-path cost: the stamp is a single vectorized int64 increment per write batch.

Addresses #4041.

theap06 added 2 commits July 24, 2026 00:21
Executable spec for step 1 of the conditional replay-update RFC:
round-robin writers stamp each slot with an int64 generation exposed
via writer.generations_of(index); first write is generation 0, every
slot reuse increments it, empty() never revives old handles, and
generations persist through state_dict and dumps with legacy
checkpoints still loading. Sampling exposes the stamps as an
index_generation info entry / TensorDict key aligned with index.

Tests are expected to fail until the implementation lands.
Round-robin writers now stamp each storage slot with an int64
generation counter: the first write of a slot has generation 0 and
every round-robin reuse increments it, so a captured
(index, generation) pair identifies one specific record and becomes
detectably stale once the slot is recycled or the buffer is emptied.
Generations are exposed through writer.generations_of(index), persist
through state_dict and dumps/loads (legacy checkpoints still load),
and are reported at sampling time as an index_generation info entry,
which TensorDict buffers surface as a sample key next to index.
Writers that do not track reuse report -1. Positional write_at and
raw __setitem__ writes patch records in place and do not advance
generations. A wraparound-heavy extend benchmark covers the hot-path
cost.

First step of the conditional replay-update RFC (pytorch#4040).
Addresses pytorch#4041.
@pytorch-bot

pytorch-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/rl/4042

Note: Links to docs will display an error until the docs builds have been completed.

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jul 24, 2026
@github-actions github-actions Bot added Feature New feature Benchmarks rl/benchmark changes ReplayBuffers labels Jul 24, 2026
@theap06 theap06 closed this Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Benchmarks rl/benchmark changes CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Feature New feature ReplayBuffers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant