Repository navigation
Conversation
CLA Signature PassGinray, thanks for your pull request. All authors of the commits have signed the CLA. 👍 |
420233f to
5d32fc4
Compare
CLA Signature PassGinray, thanks for your pull request. All authors of the commits have signed the CLA. 👍 |
ce3daea to
90ad2c4
Compare
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
[AI Review]Summary. The direction is good: replacing the packed format with frame-native scatter WRITE is a net simplification. In its current form, though, I'd suggest addressing two issues before merging: a performance regression on the default ZMQ backend, and a failure mode where one uncertain transfer permanently disables a client ↔ storage-unit path with no recovery owner. A few reliability risks also need validation under realistic RL patterns, and the change would be much easier to review if split. Locally, Scope note. Against Should fix before merge1. Default ZMQ backend: GET responses are serialized twice. TransferQueue/transfer_queue/storage/simple_storage.py Lines 1187 to 1198 in 90ad2c4 TransferQueue/transfer_queue/storage/simple_storage.py Lines 1049 to 1050 in 90ad2c4 For GET this walks the whole payload twice on the single storage worker thread (non-contiguous tensors are copied by 2. One uncertain failure permanently disables a path, with no recovery. TransferQueue/transfer_queue/storage/payload_transfer/nixl.py Lines 343 to 347 in 90ad2c4
For memory safety, quarantining the one receive buffer is already sufficient: it is never reused, and later GETs use a fresh transfer_id and buffer, so a late WRITE can only land in the quarantined memory. GET is read-only, so it can also be retried after quarantine. Suggest dropping the per-target permanent failure in favor of a byte budget on quarantined memory (fail fast only when exceeded). On the sender side, either reset the remote agent or fail the job explicitly rather than leaving a half-working client. Reliability risks to validate3. On storage units, all stored data becomes registered (pinned) memory, retained per whole PUT. TransferQueue/transfer_queue/storage/simple_storage.py Lines 276 to 277 in 90ad2c4
The 1000-cycle validation runs PUT/GET/CLEAR in lockstep, which is the one pattern that hides this. (The previous path copied into one per-PUT bytearray, so retention granularity was the same, but the memory was not pinned.) Consider keeping zero-copy on the client GET path only and copying out on storage-side PUT commit, which also makes the storage cache effective. At minimum, document this and validate with many outstanding PUTs. 4. The sender may remove and re-add the remote agent on almost every PUT. TransferQueue/transfer_queue/storage/payload_transfer/nixl_ucx_runtime.py Lines 262 to 264 in 90ad2c4 TransferQueue/transfer_queue/storage/payload_transfer/nixl_ucx_runtime.py Lines 646 to 652 in 90ad2c4 With storage-side buffers held long-term (point 3), nearly every PUT presents new metadata. Whether this rebuilds UCX endpoints/QPs depends on NIXL, but "QP count stayed at 2" was measured with one buffer reused 999 times. Please check QP/FD counts with many un-cleared PUTs. If NIXL supports loading partial metadata incrementally for a known agent, the 5. O(n²) overlap scan under the global runtime lock. TransferQueue/transfer_queue/storage/payload_transfer/nixl_ucx_runtime.py Lines 516 to 527 in 90ad2c4 Jagged nested tensors are unbound into one frame per sample on PUT. Timing this scan alone: 0.22 s for 4096 frames and 0.86 s for 8192, all while holding the runtime 6. Pending PUT/GET entries never expire. Simplicity and maintainability
Interfaces
Test gaps
|
[AI Review] Follow-up: how common RDMA stacks manage registered memoryThis expands on point 3 above. Common RDMA stacks handle registered memory in roughly three ways, and none of them registers a fresh buffer per request and then lets the lifetime of stored data decide when that buffer is freed. 1. Communication libraries (MPI, UCX, NCCL, TensorPipe, brpc): library-owned bounce buffers, user memory for large messages. 2. Transfer engines (NIXL, Mooncake Transfer Engine, 3FS USRBIO): the application provides registered memory. 3. RDMA storage (Mooncake Store, FaRM, 3FS): one fixed pool registered at startup, allocated per object. Where this PR differs. On storage units, all three conditions are inverted:
The client GET lease is milder, since consumers usually release a batch quickly, but a caller who keeps one small slice still pins the whole GET buffer. There is already a precedent in this repo. The MooncakeStore read path (#175) follows the first pattern: a bounded pre-registered pool, one lease per read, an immediate copy into the caller's tensors, then release. The lease ends with the transfer, not with the data: TransferQueue/transfer_queue/storage/clients/mooncake_client.py Lines 583 to 598 in 90ad2c4 Suggestion.
|
## Motivation Bring the rdma branch up to date with main and verify that the upstream changes remain compatible with the NIXL-UCX payload path. ## Summary - Merge main changes into rdma through the synced base of this PR. - Integrate upstream updates to SimpleStorage, storage request retries, ZMQ socket handling, SSD offload, and Mooncake buffer management. - Fix merge regressions in SSD-backed NIXL payloads, ZMQ error handling, and storage arrival metrics. - Update the payload-transfer contract: `PayloadTransfer.put()` returns the transferred payload byte count, and `PayloadTransferTimeout` carries control-plane timeouts to the storage manager for retry handling. - Enable retry handling for NIXL control-plane timeouts; WRITE/runtime failures remain non-retryable. Conservative cancellation and transaction-phase retry guards are handled in follow-up #176. - Preserve the existing SimpleStorage APIs and optional NIXL-UCX backend. ## Validation The following checks were run against this merge to verify regression coverage and compatibility with the NIXL-UCX path. - Core regression suite: 702 passed, 10 skipped. - Focused tests for storage retries, accept probing, ZMQ socket pooling and shared contexts, Mooncake buffer management, and SSD offload passed. - ZMQ retry passed with an injected lost response; the retried request returned the expected payload. - Two-node NIXL-UCX PUT/GET/CLEAR and payload integrity checks passed over the RDMA transport path. - NIXL+SSD public API checks passed for `kv_put`, `kv_batch_get`, and `kv_clear`. - NIXL timeout/cancel/retry control-flow check passed with a simulated peer. - Verified that SSD-offloaded NIXL payloads no longer retain the shared receive buffer through small Tensor / NumPy views. - Verified ZMQ timeout retries emit only retry warnings and log one final error after exhaustion; peer errors are logged and wrapped only once. - Added regression coverage for ZMQ send / receive timeouts, key-not-found handling, and ZMQ / NIXL arrival metric semantics.
Signed-off-by: Yinlei Sun <ginray0215@gmail.com>
90ad2c4 to
517c7a4
Compare
|
Thanks for the detailed review. I have pushed the follow-up fixes for the two pre-merge findings and several related lifecycle issues.
Validation of the pushed code tree: On each of two servers, the focused NIXL lifecycle, metrics, payload-transfer, and SimpleStorageUnit suites passed (190 passed, 2 warnings), and Remaining scope: Client GET lease growth, partial-metadata reloads under varied buffer sizes, small-frame registration and lock contention, and large-payload copy cost still need workload-scale measurements. The quarantine budget stops new receives after its threshold; it is not a strict cap on a transfer already in flight. |
Motivation
PR #163 introduced the
PayloadTransferabstraction and the optional NIXL-UCX Host-to-Host payload backend forSimpleStorage.This PR further optimizes and hardens the NIXL-UCX path for RL workloads. The main goals are to reduce avoidable Host-side copies, support large frame-native payloads, avoid blocking
SimpleStorageworkers during GET transfers, and make buffer / transfer lifecycle handling safer under concurrency and failure.This work follows the optimization plan outlined in the Payload Transfer roadmap.
Summary
receive_buffer_cache_mbper runtime.SimpleStorageworker to continue processing requests while the NIXL transfer completes.Design
ZMQ remains the control plane, while non-empty Host payloads use NIXL-UCX.
The implementation intentionally keeps the scope limited to the
SimpleStorageHost payload fast path rather than introducing a general-purpose NIXL transport or MR management subsystem.Compatibility
SimpleStoragepublic APIs remain unchanged.Validation
Validation was performed on the current PR head with both two-node NIXL-UCX tests and local RL integration tests.
rma(rc_verbs/hns_0:1), including PUT / GET / CLEAR, datatype and nested-object handling, receive-buffer lease reuse, same-peer / different-peer concurrency, and controlled peer failure.data_ptr()addresses, with matching GET contents and SHA-256 hashes. Existing receive MRs were reused as send sources without another registration. No payload-sized staging copy was observed; small metadata frames were still copied.Follow-up
Continue reviewing whether the current lifecycle and failure-handling design can be simplified without weakening correctness.
Further validate performance and memory behavior in larger-scale distributed RL workloads.
Consider adding a resource limit to the idle registered receive pool. This may depend on the results of integration with RL frameworks, and I have not yet decided on the right approach.Current status: implemented a bounded idle receive-buffer cache with
receive_buffer_cache_mb(default: 256 MiB per runtime); oversized transient buffers are released after their leases end.Connect the runtime diagnostics to the existing TransferQueue metrics / Prometheus path.Current status: payload runtime diagnostics are exported through the existing metrics path as
tq_storage_payload_*metrics.