Repository navigation
[Core] Add step-gated record drain and build rings on use - #168
Conversation
RingConfig.drain_policy = "step_gated" stops the drain from flushing on its own. RecordRuntime.allow_flush(), called once per model step after the step's producers, records an event on the current stream and grants the drain the ring records reserved since the previous call; the drain waits on that event, then flushes them. Beginning a step with ungranted records raises; legacy rings refuse the policy. MonitoringEngine no longer builds a ring in its constructor: the first legacy entry point builds the legacy ring, create_record_runtime builds the record ring, so recording never builds a ring only to replace it.
The call grants one step's records and closes that step; the name now says so and pairs with begin_step().
Samfisheryu
left a comment
There was a problem hiding this comment.
I reproduced two regressions below that should be fixed before merging. Validation passed: 363 targeted Python tests, 26 GPU tests, 1,650 records through wraparound/forced flushes, and 100 CUDA Graph replays. Storage-extension tests were partly skipped; this was not a full storage-chain check.
…re off The legacy ring is built on the CUDA device that was current when the engine was configured, where the constructor used to build it, not on the device current at its first use. allow_flush_step() also grants while capture is disabled: disabling stops new reservations only, so records reserved before it still need their grant.
Samfisheryu
left a comment
There was a problem hiding this comment.
Rechecked a1e6240 using the original repros. Both issues are fixed: the lazy legacy ring stays on its construction device, and disabling capture no longer drops a pending step's grant. The next begin_step succeeds and the record is delivered once.
363 targeted Python tests and 28 GPU tests passed, including both new regressions on two GPUs. Four storage-extension tests were skipped. No new blocking issues found in the fix. CI is still running.
Brings in the step-gated record drain and on-use ring construction (#168). The conflicts were additions next to the mirror-only ring; both are kept, and mirror-only rings return before the step-gated checks.
Summary
Add an opt-in drain policy for record rings in which the integration, not a timer or ratio threshold, decides when records are flushed: once per model step, after the step's producers. Also make
MonitoringEnginebuild a ring only on first use, so a recording engine no longer builds a legacy ring just to replace it.Configuration
allow_flush_step()grants the drain every ring record reserved since the previous call; it records an event on the current stream and returns without waiting. A step with no ring records records no event (including a step whose only records were oversized and went through the CPU-direct path). Callingbegin_step()while the previous step still has ungranted ring records raises.step_gatedis for record rings only: a legacy ring refuses it; mirror-only rings have no drain.Implementation details
flush_and_wait(), shutdown) may deliver granted records early, and later grants then flush only what is still belowallowed. No duplicates, no lost records.ring_capacities(),capture_enabled,set_capture_enabled()) builds the legacy ring;create_record_runtime()builds the record ring directly.enable_ring_transport()still builds at once;close()and a failed record-ring creation leave no ring configured, as before.Compatibility and scope
"autonomous"(the default) keeps the existing drain behavior. The only observable change for existing users is ring construction timing: code readingengine._ring_transport/engine._ring_engineimmediately after construction, before any entry point, now seesNone. One test helper (test_eager_safety_net_gpu.py) is updated accordingly; the HF adapter,BackendAdaptorsubclasses and the released vLLM integration go through the entry points.Validation
test_rings,test_ring_engine,test_null_mode,test_producer,test_record_consumer).botocore, as onmain).test_e2e_correctness_vs_hffailures also occur onmain.test_record_step_gated_drain_gpu.py: 12 passed — no flush without a grant, per-step grants, event wait without host synchronization, ordered grants, empty steps, the ungranted-records error, forced flush without duplicates, oversized records in order, policy validation, ring construction on use for both policies.Performance
Engine plus record-runtime setup, 1 GiB payload ring and 1 GiB pinned staging, sampled outside the process:
Step-gated: with ~256 ms of GPU work queued ahead of the producers,
allow_flush_step()returns in ~14 µs and records reach the sink only after the GPU passes the step's event.Setup: one RTX 4090, PyTorch 2.13.0+cu130, CUDA 13.0, Python 3.12.