[Feature] Add generation-stamped replay buffer slots - #4042
Closed
theap06 wants to merge 2 commits into
Closed
Conversation
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.
🔗 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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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-1and are exempt from the sampling surface via atracks_generationsflag.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).state_dict/load_state_dictanddumps/loads(memmap sidecar next tometadata.json); checkpoints from before this change still load.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_atand 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).extendthat wraps the storage and writes a slot twice advances its generation once.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) andTestSampleGenerationInfo(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. Fulltest/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.