[Feature] Implement Compare-Version facility for update_if_present - #4049
[Feature] Implement Compare-Version facility for update_if_present#4049coder-jayp wants to merge 7 commits into
Conversation
🔗 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. |
4470491 to
1a3e52c
Compare
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>
1a3e52c to
407ae4e
Compare
|
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. |
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).
Types of changes
Checklist