[Feature] Add replay readiness waits and counters - #4408
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/rl/4408
Note: Links to docs will display an error until the docs builds have been completed. ❌ 4 New FailuresAs of commit b874303 with merge base 2d258fe ( NEW FAILURES - The following jobs have failed:
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
4b2fd00 to
3f7b993
Compare
3f7b993 to
fcf49ec
Compare
fcf49ec to
bbce8ff
Compare
vmoens
left a comment
There was a problem hiding this comment.
Review (COMMENT) — own PR, comment-only.
Read the whole thing carefully, including the concurrency choreography. The core design holds up:
- Notification placement is right:
_notify_replay_state_change()is called after releasing_replay_lock/_write_lockinadd/extend/empty, so there's no lock-order inversion with the condition variable, and thecancel_eventpolling capscondition.waitat 0.1s only when cancellation was requested. __init__delegates toshare(), so both shared and non-shared buffers always have a usable_readiness_condition(I went looking for the "plain buffer never gets one" bug — it isn't there).- Pickle discipline is thorough: spawn-inheritance keeps
Value/Conditionhandles, checkpointing snapshots them to plain ints,__setstate__reconstructs pershared, andload_state_dict/dumps/loadsround-trip is pinned by tests. - The
done-vs-terminated-style split betweenstorage_sizeandsampleable_sizeplus 64-bitwrite_countmigration (with old-pickle compat via plain-int payload) is exactly what long-running async training needs. RemoteTensorDictReplayBufferwaits run on the owning process where RPC-issuedextends also land, so server-side waits do get notified — good.
Findings, in descending order of importance:
-
Service-backend / distributed-transport clients silently hang with
timeout=None. The docs say Ray and "the fixed-layout distributed transport" don't support blocking readiness, but onlyRayReplayBuffer/_RayReplayBufferClientactually raise. A_DistributedReplayServiceclient is aReplayBuffersubclass whose local storage never fills (writes go over the wire), sosample(wait=True)/wait_until_sampleable()on it blocks forever with the defaulttimeout=None— no error, no wakeup, since no local notification ever fires. Either raiseNotImplementedErroron those clients like Ray does, or implement a control-channel polling loop (the stats channel you just extended has everything needed). Silent infinite hang is the worst of the three options. -
sizesemantics diverge between base and ensemble. Base now reports"size" = sampleable_size(docstring: "mirrorslen(buffer)"), butEnsembleReplayBuffer.stats()["size"]remains the physical storage count while also exposing a different"sampleable_size". Consumers diffingstats()["size"]againststats()["sampleable_size"]to detect consumption pressure get something meaningful onReplayBufferand a no-op on the ensemble. Either make ensemble's"size"mirrorlen(self)too, or document the divergence on the ensemble side. -
sample(wait=True)is advisory, not a reservation. With a consuming sampler and two concurrent learners, both can passwait_until_sampleableand the second still fails inside_sample. That's inherent to check-then-act across processes, but the docstring should say so explicitly — async learners will otherwise treatwait=Trueas a guarantee. -
Minor:
test_stats_counters_checkpoint_roundtrip'sdumpsleg extendsrestoredwith a zero row beforeloads— if that's just to trigger storage init, a comment saying so saves the next reader a head-scratch.
|
CI triage for
These exact failures recur on the independent #4407 head and on the stacked #4418/#4419 heads. Neither failure exercises this PR's replay-readiness/counter changes, so I found no branch-caused CI failure to patch. |
No description provided.