mirror of
https://github.com/kvcache-ai/Mooncake.git
synced 2026-11-05 01:17:55 +00:00
[GH-ISSUE #5042] [Feature Request]: [RFC] TCP transport: pinned GPU staging pool to overlap staging copies with socket IO #1277
Labels
No labels
ascend
auto-closed
bug
bug
CI/Build
Common
documentation
enhancement
feature request
good first issue
help wanted
high priority
MetaX
nightly-failure
pull-request
PyTorch Backend
question
RFC
Roadmap
stale
Store
Tests
Transfer Engine
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
kvcache-ai/Mooncake#1277
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @Czzzzz-5 on GitHub (Oct 28, 2026).
Original GitHub issue: https://github.com/kvcache-ai/Mooncake/issues/5042
Summary
The TCP transport serializes synchronous 64 KiB staging copies with socket I/O, capping effective GPU-buffer transfer bandwidth at ~1.5 GB/s. This RFC proposes a persistent pinned-memory staging pool (one non-blocking stream + event per slot) for the TCP transport, overlapping staging copies with socket I/O, plus engine-side batched gather/scatter APIs for scattered GPU segments (e.g. vLLM KV caches). Measured 1.6–2.1x speedup on 128k-token KV transfers (2× RTX 5090, TCP loopback), raising effective bandwidth to ~3.1 GB/s.
Motivation
The TCP transport stages GPU buffers through a host staging buffer one
64 KiB chunk at a time: a synchronous
cudaMemcpy, thenasio::async_write/async_read, then the next chunk(
ServerSession::writeBody,ClientSession::readBody). Within onelane, staging copies and socket I/O are strictly serialized.
Measured with per-chunk timing instrumentation on a loopback
KV-transfer benchmark (2x RTX 5090): ~14% of a lane's timeline is
spent in staging copies while the socket idles, capping effective
bandwidth at ~1.5 GB/s.
#3562 (reuse TCP CUDA staging buffers across chunks) removes per-chunk
allocation but keeps the copy→IO serialization; #3724 shards sessions
across io_context threads. Neither removes the serialization within
a single transfer. (Related: #5012 recently added an opt-in pinned
staging pool to the io_uring transport — this RFC proposes the same
idea for the TCP transport.)
Proposal
Add a pinned-memory staging pool
(
transport/tcp_transport/gpu_staging_pool.h): N persistent pinnedslots, each with a non-blocking stream + event.
asynchronous D2H copy, then chunks are streamed to the socket
directly from the pinned slot.
with a single H2D copy before COMPLETED is reported, preserving the
"applied at the destination" semantics of the v2 protocol.
submit_gather_write(multi-segment GPU gather into a slot + singlecontiguous write, e.g. a vLLM KV cache spread over blocks),
receive-side scatter, both using
cudaMemcpyBatchAsync, pluslane-parallel split writes to consume multiple TCP lanes for one
transfer.
/ all slots busy → existing per-chunk path (one-time WARNING, no
silent behavior change).
Env knobs:
MC_TCP_GPU_STAGING(0disables),MC_TCP_GPU_STAGING_SLOT_MB(default 16 MiB),MC_TCP_GPU_STAGING_SLOTS(default 8).Benchmark
Real-shape KV transfer bench (P/D processes over TCP loopback, KV
layouts matching vLLM v1; 16 token levels x 3 iterations + warmup;
receiver-side data-pattern verification; same-day A/B against main @
e58ad953):Effective bandwidth: ~1.5 -> ~3.1 GB/s.
Downstream evidence (vLLM PD-disaggregated serving with a connector
using these APIs): 100k-token prompt (5.7 GB KV) transfers 4/4 where
the legacy per-descriptor path deadlocks; network phase 2.4-2.9 s ->
0.31-0.41 s.
Scope of the accompanying PR
~920 LOC,
mooncake-transfer-engine/src/transport/tcp_transport/+mooncake-integration/transfer_engine/. AI-assisted porting/benchmarkanalysis; the human submitter has reviewed the full diff.
Before submitting a new issue...
@github-actions[bot] commented on GitHub (Oct 28, 2026):
Thanks for opening this issue, @Czzzzz-5!
293024653A maintainer will triage this when possible. To help us respond faster, please include:
Useful links: Documentation · Contributing guide
@he-yufeng commented on GitHub (Oct 28, 2026):
Two things from working on the same staging path, since they change the baseline this RFC is measured against and one of them is a correctness constraint rather than a performance one.
The synchronous
cudaMemcpydescribed in the motivation is on its way out. #4324 moves the staging copy onto a private non-blocking stream and keeps a sync only for the copy's own completion contract, so it no longer queues behind every kernel on the caller's lane. That is a latency and correctness fix rather than the overlap this RFC is after: staging and socket I/O are still serialized, so the gap you measured is still there and the pool idea still composes on top of it. Worth reading the timing numbers as measured on the current synchronous path, since the split between copy time and socket idle time will shift once the copy is off the legacy stream. There is also a hard failure behind this: on real GPUs the legacy-stream staging deadlocks throughtransfer_{write,read}_on_cuda, which is what #4324 is fixing.The constraint to carry into the pool design is producer ordering. A private stream does not inherit ordering from whatever wrote the source buffer, so a slot's copy has to establish that dependency before it reads
src, or the pool turns a latency bug into a silent read of stale data. The pattern used in TENT NVLink is an event recorded and synchronized on the submitting thread's per-thread stream, once per device and submission, before any network I/O starts;SubmissionWaitsForCallerProducerBeforeStagingintcp_cuda_staging_test.cppholds a gate on that stream and asserts both that submission waits and that the staged bytes match. A per-slot stream plus event makes this easier to get right than the current single staging buffer, since each slot can own its dependency instead of relying on the lane's ordering.The other half of the motivation, batched gather and scatter for scattered GPU segments, is separate enough from the staging pool that it might be worth splitting. The pool is confined to the TCP transport session; the gather and scatter APIs are engine-side and touch the request surface.