mirror of
https://github.com/kvcache-ai/Mooncake.git
synced 2026-11-05 01:17:55 +00:00
[GH-ISSUE #3446] [RFC]: TCP Transport Optimization for Non-RDMA Fallback #881
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#881
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 @zhtshr on GitHub (Aug 14, 2026).
Original GitHub issue: https://github.com/kvcache-ai/Mooncake/issues/3446
Changes proposed
Summary
Make the classic TE
tcptransport a usable high-performance fallback onclusters without RDMA, not just a functional last resort.
Today's bottlenecks are structural, not protocol-level: grouped transfers are
strictly serialized (one slice in flight), the transport runs on a single
io_contextthread, and connection setup blocks that thread. This RFC fixesthose three plus socket tuning and connection-pool hardening.
No wire-format change — all work is scheduling, threading, and socket
config; a patched side interoperates with an unpatched peer (v1 and v2, both
directions). Split into 2 PRs. Does not overlap
rdma_twosided(#3377), whichtargets RDMA-equipped clusters.
Motivation
tcpis the lowest-priority transport (selectTransport:tcp=1 < rdma=2),so it only carries traffic where RDMA is absent — exactly where it must not be
a cliff. Four problems:
startTransferSequenceadvances oneslice at a time; under v2 the next slice waits for the peer's ack. Effective
throughput is
slice_size / RTTregardless of link speed — 128KB over 100μscaps at ~1.3GB/s.
accept, every inboundreadBody,and every outbound transfer. One core tops out at ~5–8GB/s.
getConnectioncallsresolve()/asio::connect()synchronously on the io thread; one DNS/SYN timeout(1s/3s) freezes the whole transport,
acceptincluded.TCP_NODELAYis set, so pipelining dependsentirely on
SO_SNDBUF ≥ BDP, which default autotuning misses on high-BDPlinks (100Gbps × 2ms = 25MB).
Two cheap side-fixes: the 17B
SessionHeaderis a separateasync_write(atiny packet per slice under
TCP_NODELAY), and CUDA staging does anew/deleteper chunk in all four directions.Not a problem: the CPU path is already zero-copy (
writeBodyhandsaddr + offsetstraight toasync_write). Only the CUDA branch stages throughhost memory. So "zero-copy" here means dropping unnecessary chunking and
reusing staging — not removing a copy.
Non-goals
kOpcodeV2Flaguntouched);
v3batching is deferred, see Open questions.SO_ZEROCOPY— needs source pages valid until the error-queue completion,conflicting with releasing the buffer at
COMPLETED; also a loss <256KB.io_uring/coro_io) — bottleneck is concurrency andbuffer sizing, not syscall cost.
Architecture
Current: single thread, window = 1, blocking connect.
Proposed: bounded concurrent window over N streams on an
io_contextpool.Two invariants keep it cheap:
io_context; sessions already usesocket_->get_executor()(not a globalcontext), so
Client/ServerSessionneed no locks and no logic change. Theonly global-context reference — the grouped continuation post — moves to the
socket executor.
pipelining one socket. The server sees the same "header then payload"
sequence, so
ServerSessionis untouched and old peers interoperate.Adaptive window (no workload mode):
window = min(pending, N). Largetransfers have few slices → narrows to 1–2 streams (avoids send-buffer
contention/incast); small transfers have many → fills N to hide ack RTT.
Chunking varies only by memory type (CPU: none; CUDA: staged), not by size.
Default
N=4(4 × 4MB ≈ 16MB in flight, spans 0.3MB–12.5MB BDP). Dispatch is ashared atomic cursor (work-stealing) to avoid stranding on uneven slices.
Roadmap (2 PRs)
Benchmarking goes first: there is currently no benchmark, so without a
baseline PR2's gains are unquantifiable and regressions invisible. PR1 is also
low-risk (tuning), while PR2 bundles the concurrency work under one review.
PR1 — Baseline + socket/pool tuning.
tcp_transport_bench(configurable slice size/count/streams; throughput +P50/P99;
tc netemRTT).SO_SNDBUF/SO_RCVBUF(MC_TCP_SOCK_BUF, 4MB,0=autotune),TCP_QUICKACK, optionalTCP_CONGESTION.MC_TCP_POOL_MAX_PER_PEERcap;cleanupIdleConnectionsoff the locked path onto a timer.
PR2 — Concurrency: multi-stream, threading, async connect.
submitTransfer/Taskroutedthrough it to bound their unlimited fire-and-forget loop.
async_write(wire bytes identical);CPU path stops chunking; CUDA staging reused per session.
io_contextpool (MC_TCP_IO_THREADS, lazy), each socket pinned to one ctx;getConnection→asyncacquireConnection(resolve cache; fixes the blocking-connect defect);
session->initiate()on the socket executor.readWriteAck()/writeBody()order verbatim); failingresolve/connectno longer stalls others; disconnect / in-flight-destruction cases; throughput
vs PR1 — small slices scale ~N, no regression at 8MB.
Alternatives considered
SO_ZEROCOPYCOMPLETED; loss <256KB.io_uringbackendcoro_iorebuildv3batch headerResolved decisions
cap; reuse removes per-transfer reconnect).
MC_TCP_SLICE_SIZEstays64KB — post-PR2 it only governs CUDA staging, so unchanged is a CPU no-op
and CUDA no-regression; opt into 1MB when wanted. Only the pool flip is a
documented behavior change.
MC_TCP_STREAMS_PER_PEER=4fixed; no BDP autoscaling this round.getenv, aligning with existing TCP style and avoiding mergechurn against active
rdma_*work inconfig.h.Open questions
v3batching worth it? Deferred until PR2 data shows a workload themulti-stream window can't cover; unscheduled otherwise.
Future work
synchronous
cudaMemcpy→async_write→cudaMemcpy, no overlap, ~½ devicebandwidth). Orthogonal; ~150 LOC.
v3multi-descriptor batching, if PR2 justifies it.Ask
Review (a) TCP-as-fallback being worth this investment, (b) the no-wire-change
boundary (
v3deferred, not proposed here), and (c) the 2-PR order puttingbenchmarking before the concurrency work.
Related: #3377 (
rdma_twosided, a separate transport targeting RDMA-equipped clusters).@github-actions[bot] commented on GitHub (Aug 14, 2026):
Thanks for opening this issue, @zhtshr!
44193225A maintainer will triage this when possible. To help us respond faster, please include:
Useful links: Documentation · Contributing guide