Skip to content

[Feature] Implement Compare-Version facility for update_if_present - #4049

Open
coder-jayp wants to merge 7 commits into
pytorch:mainfrom
coder-jayp:feat/compare-version
Open

[Feature] Implement Compare-Version facility for update_if_present#4049
coder-jayp wants to merge 7 commits into
pytorch:mainfrom
coder-jayp:feat/compare-version

Conversation

@coder-jayp

Copy link
Copy Markdown
Contributor

Description

Implements the optional compare-version facility inside ReplayBuffer.update_if_present. This allows asynchronous updates to be safely ignored if they carry an older model/state version than what is currently stored in a live Replay Buffer slot.

Motivation and Context

This prevents an older asynchronous computation from overwriting a newer refresh of mutable replay fields. It guarantees generation-safe and version-safe updates.

Part of #4040. (Builds on top of the base API drafted in #4043).

  • I have raised an issue to propose this change (required for new features and bug fixes)

Types of changes

  • New feature (non-breaking change which adds core functionality)

Checklist

  • I have read the CONTRIBUTION guide (required)
  • My change requires a change to the documentation.
  • I have updated the tests accordingly (required for a bug fix or a new feature).
  • I have updated the documentation accordingly.

@pytorch-bot

pytorch-bot Bot commented Jul 25, 2026

Copy link
Copy Markdown

🔗 Helpful Links

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

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 25, 2026
@github-actions github-actions Bot added Feature New feature Documentation Improvements or additions to documentation Benchmarks rl/benchmark changes ReplayBuffers and removed Feature New feature labels Jul 25, 2026
@vmoens
vmoens force-pushed the feat/compare-version branch 4 times, most recently from 4470491 to 1a3e52c Compare August 7, 2026 14:50
theap06 and others added 7 commits August 10, 2026 08:55
Executable spec for step 2 of the conditional replay-update RFC:
rb.update_if_present(index=, generation=, patch=) applies every patch
key to records whose (index, generation) is still live, skips reused
or emptied slots without touching their content, and returns a result
with an updated mask aligned to the input order plus updated/stale
counts. The whole patch is validated before any write (KeyError for
unknown keys, ValueError for shape or dtype mismatches, storage
untouched in both cases), handles survive repeated updates, nested
keys are supported, ListStorage raises a capability error, multidim
storages round-trip, sampled handles flow straight into the call, and
a concurrent writer/updater stress test pins non-torn multi-key
visibility. RayReplayBuffer delegates the call to the actor.

Tests are expected to fail until the implementation lands.
…itional updates

Adds a best-effort conditional mutation API for stored replay fields.
update_if_present(index=, generation=, patch=) applies a patch only to
records whose (index, generation) pair still matches the writer's
current slot generation, skipping records whose slot was recycled or
emptied instead of corrupting them, and returns a
ConditionalUpdateResult with a per-record updated mask plus
updated/stale counts. The whole patch is validated (key existence,
shape, dtype) before any write; validation failures leave storage
untouched. The generation comparison and the patch write share one
replay-lock acquisition, giving per-record atomicity against
concurrent extends, and updating a record does not consume its handle.

Tensor storages advertise supports_conditional_update; unsupported
backends such as ListStorage raise a capability error instead of
performing an unsafe raw-index write. RayReplayBuffer delegates the
call to the actor in a single RPC (validation and write run inside the
actor under its own lock); the distributed transport raises a clear
capability error. Nested keys and multidimensional storages are
supported.

Second step of the conditional replay-update RFC. Closes pytorch#4040.
…ytorch#4046)

Drop this PR's own generation-stamp scaffold (writers.py machinery,
always-on tracking, sample-info insertions and their test patches) in
favor of the storage-owned, opt-in implementation from pytorch#4046:

- update_if_present keeps its contract but now requires a writer
  constructed with track_generations=True and raises otherwise; its
  generation comparison and patch masks are device-aware so CUDA/MPS
  storages and mixed-device handles work.
- TestUpdateIfPresent builds its buffers with tracking writers, gains a
  test for the non-tracking capability error; the superseded
  TestSlotGenerations and TestSampleGenerationInfo suites are removed
  (covered by pytorch#4046's TestWriterGeneration).
- The Ray test threads a tracking writer factory to the remote buffer.
- The wraparound benchmark enables tracking so it no longer measures a
  no-op; docs and docstrings describe the opt-in reality and link to
  the generation-stamp reference section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Executable contract for the optional versioning extension of pytorch#4040:
activation requires version_key and version together; generation-live
records apply iff version > stored (require_newer=True) or >= stored
(default); accepted records get the version written back atomically
with the patch; version_key may not appear in the patch; staleness
takes precedence and updated/version_rejected/stale partition the
batch; rejection is deterministic and side-effect free; version
accepts a scalar or a per-record int64 tensor; validation failures
leave storage and the version column untouched; without the facility
behavior is unchanged and version_rejected is None.

Tests are expected to fail until the implementation lands.
Fixes to the update_if_present version facility following review:

- Duplicate handles on the same record made the scatter write
  order-dependent (stored versions could regress while every row
  reported updated=True). Records are now deduplicated on their full
  coordinates; only the row carrying the highest incoming version is
  applied (last row on ties) and the losers are reported in
  version_rejected.
- The stored-version read is coordinate-aware, matching the write path,
  so ndim > 1 storages no longer crash or read the wrong cells.
- version_key and patch keys are unravel_key-normalized (tuple and
  string spellings of a key can no longer bypass the
  version_key-in-patch guard); dotted-string version keys are rejected
  with a clear error; the type hint is NestedKey | None and the
  redundant function-level unravel_key import is gone.
- [N, 1]-shaped version leaves and incoming version tensors are
  normalized to per-record scalars; genuinely non-scalar version fields
  raise a clear ValueError at entry.
- version_rejected is allocated whenever versioning is active, so an
  all-stale call no longer returns None for it.
- Version comparisons and masks are storage-device-aware (CUDA/MPS).
- The three new kwargs are threaded through the RayReplayBuffer client
  signatures; docstrings (update_if_present, ConditionalUpdateResult)
  and the replay-buffer reference docs cover the facility; whitespace
  and function-level imports cleaned; tests cover duplicates, [N, 1]
  version leaves, tuple/dotted version keys, multidim storages and the
  all-stale case, with buffers built on opt-in tracking writers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vmoens
vmoens force-pushed the feat/compare-version branch from 1a3e52c to 407ae4e Compare August 10, 2026 07:55
@vmoens

vmoens commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Reviewed the rebased head 407ae4e, including the latest hardening commit. The current implementation normalizes nested key spellings, reads/writes full coordinates for multidimensional storage, resolves duplicate handles by highest incoming version with last-row tie-breaking, and keeps stale/version-rejected masks disjoint even for an all-stale call. I ran the focused core suite locally: 29 update_if_present tests passed, including concurrency, duplicates, multidimensional storage, nested keys, and checkpoint round trips. The Ray delegation test was collected but skipped because Ray is unavailable here; CUDA/MPS device paths were also not exercised locally. I did not find a blocking issue in the current diff, with those distributed/device paths as the remaining validation risk.

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. Documentation Improvements or additions to documentation Feature New feature ReplayBuffers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants