mirror of
https://github.com/kvcache-ai/Mooncake.git
synced 2026-11-05 01:17:55 +00:00
[GH-ISSUE #2022] [Bug]: Segfault in submitSlicesOnPeer → fi_write when peer reconnects during an in-flight transfer (EFA) #561
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#561
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 @whn09 on GitHub (Apr 30, 2026).
Original GitHub issue: https://github.com/kvcache-ai/Mooncake/issues/2022
[EFA] Segfault in
submitSlicesOnPeer→fi_writewhen peer reconnects during an in-flight transferSummary
Under high-concurrency KV transfers on AWS EFA (SGLang 2P2D prefill, 16 NICs,
MC_MAX_WR=16384,MOONCAKE_PROTOCOL=efa, P2P handshake mode), Mooncakesegfaults inside libfabric's
fi_writea short while after a peer changesits P2P handshake port (the normal "random port per run" behaviour):
Called from SGLang's
transfer_worker→send_kvcache→batch_transfer_sync.Reproduction
kvcache-ai/Mooncake@b0bda8c(main, 2026-04-30)protocol=efa, metadata =P2PHANDSHAKE, hugepages=65536×2MBMC_NUM_CQ_PER_CTX=4,MC_MAX_CQE_PER_CTX=65536,MC_MAX_WR=16384,FI_EFA_USE_HUGE_PAGE=1,FI_EFA_USE_DEVICE_RDMA=1topology
pp2 tp8 cp8 deepep8(prefill) +tp16 dp16 deepep16(decode),then
vllm bench serveequivalent of 180 prompts, 3K input / 1K output,rate=18 req/s.
The segfault occurs consistently at the end of the benchmark burst when
one of the 16 decode DP ranks rebinds its Mooncake RPC port (random port
selection in P2P handshake mode is documented in
common.h::normalizeNicPath), triggeringEfaEndPoint::setPeerNicPathto take the CONNECTED-branch disconnect.Root cause hypothesis
EfaEndPoint::setPeerNicPath(efa_endpoint.cpp:31-40):disconnectUnlocked()tears down AV entries / endpoint state associatedwith the old
peer_fi_addr. But a concurrentEfaContext::submitSlicesOnPeer(efa_context.cpp:813) takes its own
post_lock_spinlock and then callsfi_write(shared_ep_, ..., peer_fi_addr, ...)with apeer_fi_addritlatched earlier from
EfaEndPoint::peer_fi_addr_:post_lock_(onEfaContext) andEfaEndPoint::lock_are differentlocks, so disconnect and post_send are not mutually exclusive. Libfabric
dereferences the torn-down AV entry → segfault.
Why it shows up now
documented behaviour in
common.h:476-488). So periodically, especiallywhen a peer worker process re-initialises a Mooncake RPC socket, the
peer_nic_path_string changes (same IP + same NIC, different portsuffix).
MC_MAX_WR), the probability of a pendingfi_writebeing in flightwhen
setPeerNicPathfires is high.tp16 dp4 moe_a2a=noneon thesame hardware, same benchmark) did not reproduce.
Proposed fix directions
setPeerNicPathacquire/shareEfaContext::post_lock_around the
disconnectUnlocked()call, so it cannot race within-flight
submitSlicesOnPeer.old peer (drain pending WRs) and only then remove AV entries.
peer_fi_addrinEfaEndPointand have thesender path hold a reference across
fi_write, letting disconnect blockuntil all refs drop.
Option A is the smallest change but couples two lock hierarchies. Option
B is cleanest semantically but adds latency to legitimate reconnects.
Workarounds for users hitting this today
fewer unique
(peer_ip, peer_port)tuples exist.P2PHANDSHAKEso portsdon't change per run — but note that
getDefaultHandshakePort()is currently bypassed by the P2P path (each process calls
findAvailableTcpPort()regardless); using etcd won't stabilise portsunless combined with
MC_LEGACY_RPC_PORT_BINDING.after all prompts in a burst complete, so steady-state latencies are
captured correctly before the segfault takes down the server.
@whn09 commented on GitHub (Apr 30, 2026):
Follow-up: the reconnect path itself is transport-agnostic
Confirmed by reading source that all three transports implement
setPeerNicPathwith the same disconnect-on-CONNECTED branch:efa_endpoint.cpp:31-40):LOG(INFO) "Peer reconnected with new address"rdma_endpoint.cpp:140-147):LOG(WARNING) "Previous connection will be discarded"urma_endpoint.cpp:681-688): same as RDMASo users on RoCE/IB and Kunpeng see the same reconnect/teardown behavior
whenever a peer rebinds its P2P handshake port.
What makes EFA particularly segfault-prone is a structural difference:
RdmaEndPoint::disconnectUnlocked()tears down the endpoint's ownQP, isolated from other endpoints on the same context.
EfaEndPoint::disconnectUnlocked()removes entries fromEfaContext::av_, which is shared across all endpoints hosted bythat context. A concurrent
fi_write(shared_ep_, ..., peer_fi_addr, ...)on another endpoint hits the stale AV entry and segfaults.The race window exists on RDMA too, but crashing requires the stale QP to
outlive a post_send from the same endpoint (harder to hit because each
endpoint has its own QP), whereas on EFA any concurrent transfer to the
same peer over any of the ~16 EFA NICs can trip it.
This also explains the log frequency asymmetry: an RDMA deployment has
1-2 endpoints per peer, so one port change logs 1-2 lines; an EFA
deployment has one endpoint per (local NIC, peer NIC), so 16 NICs × 16
peer NICs can log hundreds of lines for the same physical port-change
event.
Suggests any fix should consider both transports — RDMA is latently
racy even if not currently observed to crash.