mirror of
https://github.com/kvcache-ai/Mooncake.git
synced 2026-11-05 01:17:55 +00:00
[GH-ISSUE #3322] [RFC]: MPComm Transport for TENT — an RDMA-native multi-rail transport backend #843
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#843
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 @c-guo16 on GitHub (Aug 7, 2026).
Original GitHub issue: https://github.com/kvcache-ai/Mooncake/issues/3322
Changes proposed
Summary
This RFC proposes adding MPComm as a TENT transport backend, registered as
TransportType::MPCOMM.MPComm (Memory Pool Communication) is an RDMA-native data-plane library for large-scale
heterogeneous memory pools, open-sourced by the Tencent Astral Network Team under Apache-2.0:
https://github.com/Tencent/UCL-MPComm. It drives multiple RDMA NICs concurrently with two-level
load balancing (across NICs, and across QPs within a NIC) and NUMA-aware worker placement, exposing
one-sided
put/getprimitives.Data path:
Validated scope is host DRAM and GPU device memory over RoCE/IB, in both directions and in
mixed combinations. GPU memory is registered through
nvidia-peermem, the same mechanismRdmaTransportdepends on for GPU-Direct. The implementation is complete and validated; this RFCexists because the change exceeds the 500-LOC threshold in
CONTRIBUTING.md("For major architectural changes (>500 LOC excluding tests), we would expect a GitHub issue (RFC)").
Related roadmap: #1058 (Transfer Engine NEXT).
Motivation
The obvious question is why a second RDMA transport when
RdmaTransportalready exists. Threereasons:
1. Measured throughput advantage over the existing TENT RDMA path.
Numbers published in the MPComm repository (2 nodes, single NUMA, 4x CX-7, dual-plane 2x200G bond
per NIC, batch=4, 1GB block):
The gain comes from aggressive multi-rail striping plus per-QP outstanding-request tracking, i.e.
from scheduling policy inside a purpose-built library, not from a different verbs API.
2. It gives TENT a second, independently-developed RDMA implementation.
Where
RdmaTransportis Mooncake's own stack, MPComm is maintained and tuned outside the project.Having both lets deployments pick per workload, and makes it possible to cross-check TENT-level
behaviour against an independent data plane.
3. Vendor-library integration is an established pattern here.
sunrise_link,kunpeng_ub/ UB (#3093, #3282), TPU and EFA are all transports that wrap anexternal SDK behind the TENT
Transportinterface. MPComm follows the same shape.Related work and how this differs
RdmaTransportUSE_*gate, enum appended). Different division of labour — see the trade-off section below.sunrise_linkcommon.cmake. Layout and CMake style were copied from it.Proposed design
Architecture
Division of labour, and the trade-off worth discussing
This is the part I most want feedback on, because it differs from the UB RFC on purpose.
#3093 states the invariant "TENT owns scheduling and policy; the URMA adapter only performs native
operations and resource lifecycle." MPComm cannot follow that invariant: slicing, NIC selection, QP
selection, outstanding-request accounting and worker threads are inside the library, and they are
precisely where its throughput advantage comes from. Decomposing them to hand control back to TENT
would mean forking MPComm and giving up the thing we integrate it for.
So the boundary is deliberately coarse:
What the transport does
WRITE/READrequests intoputAsync()/getAsync(), one MPComm transfer per requestTopologyregisterMemory()+publishBuffer())transport_attrs(see below)What it deliberately does not do
transport_attrs)Consequence, stated plainly: TENT's transport-agnostic facilities that require fine-grained
cooperation are not available for this transport in its first version —
supportsCancellation()returnsfalse, and there is no bandwidth estimation, no NIC loadstatistics, no use of
SubBatch'sqp_poolor progress-notification hooks. Failover and QoSfeatures depending on those hooks fall back to defaults.
I think this is an acceptable trade-off for a first version, and it is documented rather than
hidden. Open question O1 below asks whether the maintainers agree, since it is a precedent
question for wrapping any self-scheduling vendor library.
Endpoint bootstrap: reusing
transport_attrsinstead of a new control-plane messageMPComm performs its own TCP metadata handshake and needs the peer's
host:port. Rather than adding aUB-style dedicated bootstrap message, the transport publishes
<host>:<mpcomm_tcp_port>into thelocal segment's
transport_attrsunder theMPCOMMkey viaSegmentManager::updateLocal()+synchronizeLocal(), and peers read it back on first use.updateLocal()(not writing through the copy-on-writegetLocal()snapshot) is what makes thiscorrect.
MPCOMM_REMOTE_TCP_PORT.SegmentID.This keeps the metadata plane untouched. Worth confirming this is the intended use of
transport_attrs(open question O2).Alignment with the TENT
TransportinterfaceImplemented:
install/uninstall,allocateSubBatch/freeSubBatch,submitTransferTasks/getTransferStatus,addMemoryBuffer/removeMemoryBuffer,getSupportedTransportsregistration.Not implemented (fall back to base behaviour): cancellation, bandwidth estimation, NIC load stats,
custom allocator (
allocateLocalMemoryis not overridden — the transport is pass-through).File layout
Registration points touched:
types.h(enum + name/parse maps),tent/transfer_engine.h(C API macro),
transport_loader.cpp,transfer_engine_impl.cpp(+1 line),tent/src/CMakeLists.txt,tent/src/transport/CMakeLists.txt,pybind.cpp,common.cmake, benchmarkutils.cpp/tent_backend.cpp, plus docs.Enum and C API stability
MPCOMMis appended at the end ofTransportType, immediately afterUB, keeping existingserialized values stable — same rule #3282 followed. The C macro
TRANSPORT_MPCOMMis added withthe matching value.
transportTypeName()/parseTransportType()gain one line each; without them a"transports": ["mpcomm"]policy would silently parse toUNSPEC.Build integration
Gated by
USE_MPCOMM=ON, which requiresMPCOMM_ROOT.common.cmakeresolvesinclude/mpcomm.handlib/libmpcomm.sowithfind_path/find_libraryand fails at configuretime with the resolved paths printed — matching the EFA/CXI blocks. The library is linked by
absolute path, which also survives
$<LINK_ONLY:...>when downstream targets (e.g.mooncake_master) linktransfer_engineprivately. WithUSE_MPCOMM=OFFnothing is built,linked or registered.
Validation
Completed on 2 nodes with RoCE (multi-NIC, dual NUMA):
USE_TENT=ON USE_MPCOMM=ON, no-WswitchwarningsUSE_MPCOMM=OFFtebench --backend=tent --xport_type=mpcomm, 2 nodes, DRAM, READ--seg_type=VRAMon both sides), i.e.gpu_to_gpugpu_to_dram/dram_to_gpu--check_consistency=true)tent_mpcomm_transport_test(fork'd two-process WRITE+READ, byte-exact verify)transport_attrspublish/resolve path exercisedMPCOMM_REMOTE_TCP_PORTdeliberately unset, the initiator still reached the peer's advertised portCI limitation, stated up front. The functional test needs real RDMA hardware and
libmpcomm.so, so it self-skips otherwise and cannot validate the data path in CI. UB solved theequivalent problem with an injectable mock adapter plus a
ub-mockCI job (#3282). MPComm isconsumed as a shared library through a concrete API rather than an injected interface, so the same
trick needs an interposer or a seam that does not exist today. What CI can run today is the
enum/name-mapping test (no hardware). Open question O3 asks how much more is expected.
Non-goals
RdmaTransport; both remain selectable.MOONCAKE_PROTOCOL=mpcommandtransfer_engine_bench --protocol=mpcommare intentionally unsupported. TENT is where theproject is investing, so a legacy-path backend is not planned.
Open questions
scheduling and therefore opts out of cancellation / bandwidth estimation / credit integration? This
sets a precedent for self-scheduling vendor libraries and is the main thing I would like settled
before the PR is reviewed in detail.
transport_attrs+updateLocal()the intended mechanism for transport-specificendpoint advertisement, or is a dedicated bootstrap message preferred?
vendor hardware? Is a hardware-gated self-skipping test plus a hardware-free mapping test
sufficient for the first PR?
mpcomm(USE_MPCOMM,tent_xport_mpcomm), consistentwith
sunrise_link/kunpeng_ub?Implementation status
The implementation is finished and validated as tabulated above:
~550 LOC, the rest is registration wiring of 1-4 lines per file
mpcomm_transport.mdplus entries insupported-protocols.md,build.mdand the design index)I will open the PR once this RFC has been seen, rebased onto current
mainat that point. Feedbackon O1-O3 in particular would shape whether anything needs restructuring first.
Before submitting a new issue...
@github-actions[bot] commented on GitHub (Aug 7, 2026):
Thanks for opening this issue, @c-guo16!
35829535A maintainer will triage this when possible. To help us respond faster, please include:
Useful links: Documentation · Contributing guide
@alogfans commented on GitHub (Aug 7, 2026):
Looks good! Maybe you can add documentation to describe how to use it.
@c-guo16 commented on GitHub (Aug 10, 2026):
Thanks for the quick look!
The documentation is written and ships with the PR. Rather than paste it here, it is browsable on
my fork:
It is deliberately usage-oriented rather than a design write-up, modelled on
sunrise_link_transport.md:USE_TENT=ON USE_MPCOMM=ON,MPCOMM_ROOT, and the configure output to expecttransport_hint), and notes that one of the two is effectively required, sinceMPCOMMsits nearthe end of the default ordering
tebenchinvocation, a single-host variant, and the loglines that confirm traffic actually went over MPComm instead of silently falling back
detected at run time
MPCOMM_*knobs with defaultsrpc_server_hostnamecaveaton multi-homed or containerized hosts, and seven failure modes with symptoms and fixes
It is linked from
getting_started/supported-protocols.md,getting_started/build.mdand thedesign/transfer-engine/index.mdtoctree, so it is reachable from the usual entry points.The branch above has been through an extra review and test pass since the RFC was filed, so the
Troubleshooting section covers the failure modes we actually hit rather than just the happy path.
For the record, per the issue bot's checklist: the work is based on v0.3.12.post1
(
7da06d93) and validated on two nodes with RoCE (multi-NIC, dual NUMA) over DRAM and VRAMsegments, plus a single-node RDMA loopback run. It will be rebased onto current
mainbefore thePR is opened.
Happy to expand any section. Unless you see a problem with O2 (
transport_attrs+updateLocal()for endpoint advertisement) or O3 (CI coverage for a transport whose data pathneeds vendor hardware), I will open the PR with the current design and we can take the details
there.
@c-guo16 commented on GitHub (Aug 10, 2026):
cc @lqqqihang
@alogfans commented on GitHub (Aug 11, 2026):
The overall direction looks reasonable to me. My answers to the open questions would be: O1 yes, provided the missing capabilities remain explicit and policy code cannot assume them; O2 yes,
transport_attrsis an appropriate place for this endpoint; O3 mapping-only CI is not sufficient for the adapter logic; O4 keep thempcommname.A few points are worth addressing before opening the PR (assisted by CodeX, please recheck):
The branch is currently 84 commits behind
main, whereUBalready occupies wire value 11. After rebasing, please keepUB=11, appendMPCOMM=12, and add explicit wire-value assertions covering the C API and Python binding as well as the C++ enum.peer_map_is keyed bySegmentID, while MPComm connections are keyed byhost_id. Closing and reopening the same segment produces a newSegmentID, so the transport may reconnect to the same MPComm host.Remote buffer metadata is queried only on the first connection and then cached indefinitely. TENT supports registering and unregistering memory after a segment is opened, but later metadata changes never refresh MPComm's rkey cache. (***)
If
connect()succeeds butqueryRemoteBuffer()fails, the placeholder is removed and the next request performs anotherconnect(). That conflicts with the implementation's own observation that reconnecting replaces the connection and leaks resources. A state such asCONNECTED_NO_KEYSwould allow retrying only the buffer query.For hardware-free CI, I suggest a thin injectable MPComm adapter. It does not need to mock MPComm scheduling; it only needs to test the TENT boundary: endpoint publication/parsing, single-flight connection, query retry, dynamic buffer refresh, READ/WRITE mapping, error and short-transfer handling, exact-once handle release, and teardown.
It would also help to version the endpoint attribute, validate the port without
atoi(), provide separate listen/advertise addresses for multi-homed deployments, document the IPv4/security trust boundary, and pin or check the supported MPComm ABI/version.@c-guo16 commented on GitHub (Aug 11, 2026):
@alogfans Thank you for your attention!
We'll revise the code according to your comments~
@c-guo16 commented on GitHub (Aug 12, 2026):
Hello @alogfans — very useful review. All six are addressed and the branch is rebased onto
main.1. Wire values.
UBkeeps 11,MPCOMMappended as 12. Assertions on all three surfaces: the C++enum and sentinel, the C API macro checked through the conversion function rather than by repeating
the constant, and a
static_assertinpybind.cpp. Note your existingWireValuesRemainStableWithUbAppendedassertskNumTransportTypes == 12, so it needed updating inthe same commit.
2. SegmentID vs host_id. Fixed — the cache is keyed by MPComm host id, so reopening a segment
reuses the existing connection.
3. Metadata refresh. Keys are refetched when the peer's buffer set grows. Comparing a fingerprint
for equality turned out to be the wrong test: descriptors are cached per thread and expire
independently, so threads observing different sets would alternately invalidate each other's work,
each time issuing a blocking query. The comparison is coverage instead — refetch only when a range
appears that the keys do not cover. That is monotonic, and it also skips a refetch after the peer
merely unregisters memory.
4.
CONNECTED_NO_KEYS. Implemented under that name. A connection is recorded before the queryruns, so a failed query retries the query alone. Ownership is tracked separately from the state, so a
refresh in progress does not block requests whose ranges the current keys already cover.
5. Hardware-free CI. Added an
MpcommAdapterboundary followingUrmaAdapter/TpuPjrtShim,with the peer cache and the request/completion mapping moved into their own units. Only the adapter
implementation includes
mpcomm.hand it compiles to a stub without the provider, sotent_mpcomm_boundary_testis built unconditionally and needs neither an RDMA device norlibmpcomm — 26 cases, 0.1 s, mapping one-for-one onto the eight items you listed (case names in
tent/tests/mpcomm_boundary_test.cpp). The real data path stays intent_mpcomm_transport_test,which skips itself without hardware.
6. Smaller items.
v1:<ipv4>:<port>; an unprefixed value reads as v1, and the checkrequires
v+ digits +:so a hostname starting withvis not misread as a versionatoi()replaced with range-checked parsing — an invalidMPCOMM_TCP_PORTnow failsinstall()instead of silently becoming 0
addr+rkeys, i.e. everything an RDMA op needs, so that port belongs at the same trust level asthe fabric; IPv4-only is stated there
SOVERSION/SameMajorVersionthat thebuild was bypassing with a bare
find_library(). Configuration now reads it and fails on a too-oldversion or a different major
Not done — separate listen/advertise addresses. MPComm binds
INADDR_ANYwith no way to pick aninterface, so the listen side is not ours to fix; on the advertise side
rpc_server_hostnamesteersit today. Listed as a known limitation rather than half-implemented — happy to add a dedicated
setting if you want it in the first version.
On O1's condition. Cancellation, bandwidth estimation, NIC load statistics and notification stay
at the base-class defaults, so they report as unsupported rather than being assumed, and they are
listed in the doc. The GPU capabilities are no longer unconditional either — same
nvidia_peermemprobe
RdmaTransportuses, since MPComm registers device memory through plainibv_reg_mrwith nodma-buf fallback, so a host without the module is excluded during selection instead of failing at
registration.
6 commits behind
main, no conflicts. Will rebase once more and open the PR unless you see anythingelse here first.