[Data] [Core] [2.5/n] Remove BlockRefCounter.clear() and expand callback throughput benchmark - #64521
[Data] [Core] [2.5/n] Remove BlockRefCounter.clear() and expand callback throughput benchmark#64521rayhhome wants to merge 21 commits into
BlockRefCounter.clear() and expand callback throughput benchmark#64521Conversation
|
Warning Gemini encountered an error creating the review. You can try again by commenting |
There was a problem hiding this comment.
Pull request overview
Adds a symmetric “remove” API for Ray Core’s object out-of-scope/freed callback mechanism so callers (notably Python) can explicitly deregister callbacks and trigger their cleanup logic deterministically, rather than relying on callbacks becoming lazy no-ops.
Changes:
- Introduces
ReferenceCounterInterface::RemoveObjectOutOfScopeOrFreedCallbacksand implements it inReferenceCounter. - Plumbs the API through
CoreWorkerand exposes it to Python vialibcoreworker.pxdand_raylet.pyx. - Adds a C++ unit test covering “remove fires once; natural out-of-scope does not fire again”.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/ray/core_worker/tests/reference_counter_test.cc | Adds a unit test validating removal fires callbacks exactly once. |
| src/ray/core_worker/reference_counter.h | Declares the new removal API on the concrete reference counter. |
| src/ray/core_worker/reference_counter.cc | Implements callback firing + clearing for the new removal API. |
| src/ray/core_worker/reference_counter_interface.h | Adds the new API to the core reference counter interface and documents behavior. |
| src/ray/core_worker/core_worker.h | Declares a CoreWorker wrapper for the new removal API. |
| src/ray/core_worker/core_worker.cc | Implements the CoreWorker wrapper by delegating to the reference counter. |
| python/ray/includes/libcoreworker.pxd | Exposes the CoreWorker removal method to Cython. |
| python/ray/_raylet.pyx | Adds a Python-facing internal method to trigger callback removal/cleanup. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// Removes and fires all out-of-scope/freed callbacks for `object_id`. | ||
| /// Firing allows per-callback cleanup (e.g. Py_DECREF on the Python side). | ||
| /// After this call, no further out-of-scope callbacks will fire for this object. |
| """Remove and fire all out-of-scope callbacks for the given object. | ||
|
|
||
| Firing allows per-callback cleanup (e.g. Py_DECREF). After this | ||
| call, no further out-of-scope callbacks will fire for this object. | ||
|
|
clear is Called
clear is Calledclear is Called
Signed-off-by: Sirui Huang <ray.huang@anyscale.com>
Signed-off-by: Sirui Huang <ray.huang@anyscale.com>
200944a to
c3a6c68
Compare
Kunchd
left a comment
There was a problem hiding this comment.
I don't have any big concerns with this PR, but I'm struggling to determine the use case for this. We currently clean up the callback when the object goes out of scope and the callback is invoked. Why is explicit clean up necessary? Could we include the motivation in the PR description?
| for (const auto &callback : it->second.on_object_out_of_scope_or_freed_callbacks) { | ||
| callback(object_id); | ||
| } |
There was a problem hiding this comment.
Why is invoking all the callbacks neecessary before removing the callbacks? Doesn't removing the callback mean we don't care about them anymore?
There was a problem hiding this comment.
BlockRefCounter's clear() is called when StreamingExecutor calls shutdown(), which happens when a dataset execution ends. In multi-epoch training (or any jobs with many dataset executions), this would be triggered many times. Since the callbacks have Py_DECREF to preserve the _on_object_freed closures, not triggering them would result in minor leaks. What's your take on this?
There was a problem hiding this comment.
Do we expect object produced within a specific epoch to stay around after the epoch finishes?
rayhhome
left a comment
There was a problem hiding this comment.
This PR is mainly for aligning the behavior of clear() with what the function name suggests. In the current implementation, only the Data side dictionary is cleared when clear() is called. The Core callbacks will still be fired when ObjectRefs go out of scope, which seems unintuitive based on what the function is intended to do.
However, I think this does raise the question on whether we need the clear() function at all, considering that no code reads the accounting after the shutdown. Will check this offline and come back with the decision.
| for (const auto &callback : it->second.on_object_out_of_scope_or_freed_callbacks) { | ||
| callback(object_id); | ||
| } |
There was a problem hiding this comment.
BlockRefCounter's clear() is called when StreamingExecutor calls shutdown(), which happens when a dataset execution ends. In multi-epoch training (or any jobs with many dataset executions), this would be triggered many times. Since the callbacks have Py_DECREF to preserve the _on_object_freed closures, not triggering them would result in minor leaks. What's your take on this?
Signed-off-by: Sirui Huang <ray.huang@anyscale.com>
Signed-off-by: Sirui Huang <ray.huang@anyscale.com>
Signed-off-by: Sirui Huang <ray.huang@anyscale.com>
Signed-off-by: Sirui Huang <ray.huang@anyscale.com>
1b3a47e to
0477b2c
Compare
Signed-off-by: Sirui Huang <ray.huang@anyscale.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
Reviewed by Cursor Bugbot for commit 3548794. Configure here.
Signed-off-by: Sirui Huang <ray.huang@anyscale.com>
fc5e090 to
04ff126
Compare
Signed-off-by: Sirui Huang <ray.huang@anyscale.com>
Signed-off-by: Sirui Huang <ray.huang@anyscale.com>
Signed-off-by: Sirui Huang <ray.huang@anyscale.com>
Signed-off-by: Sirui Huang <ray.huang@anyscale.com>
Signed-off-by: Sirui Huang <ray.huang@anyscale.com>
Signed-off-by: Sirui Huang <ray.huang@anyscale.com>
clear is CalledBlockRefCounter.clear() and expand callback throughput benchmark

Summary
BlockRefCounter.clear()and its shutdown wiringDescription
Removes
BlockRefCounter.clear()and the_block_ref_counter.clear()call inStreamingExecutor.shutdown(). Theclear()method was added for deterministic cleanup on executor shutdown, but profiling and benchmarking showed:clear()had no measurable impact on release test benchmarks.Also expands
test_callback_throughput.py(previously only had an incremental pipeline test) with additional benchmark tests at multiple scales (100 to 10,000 blocks):on_block_producedoverhead via the Data-layer pathRelated issues
Depends on #64157 (
BlockRefCounterimplementation).Related to #63601 (prototype), #63074 (previous manual
BlockRefCounter).