mirror of
https://github.com/kvcache-ai/Mooncake.git
synced 2026-11-05 01:17:55 +00:00
[GH-ISSUE #4986] [Bug]: TENT nvlink copies are routed through GPU 0 when the first write to a peer comes from a thread on another device (7x slower) #1264
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#1264
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 @leoleoasd on GitHub (Oct 27, 2026).
Original GitHub issue: https://github.com/kvcache-ai/Mooncake/issues/4986
Originally assigned to: @TTThanos on GitHub.
Bug Report
With TENT (MC_USE_TENT=1), an intra-node write from a buffer on GPU 1 to a peer process's buffer on GPU 2 runs at ~109 GB/s instead of ~771 GB/s whenever the thread that issues the first write to that peer buffer has CUDA current device 0, which is the default for every new thread.
A self-contained script (mooncake + torch only) is attached:
tent_nvlink_ipc_device.py. It starts two processes on one node. The target registers a 1 GB buffer on--dst. The writer registers a buffer on--srcand writes it into the target 10 times from a new thread, usingtransfer_submit_write/transfer_check_status.Results on B300 (NVLink 5), median of 1 GB writes 2..10:
--bind)Before submitting...
@github-actions[bot] commented on GitHub (Oct 27, 2026):
Thanks for opening this issue, @leoleoasd!
37735580A 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):
Root cause is one line narrower than the report, and it is in the policy rather than in
relocateSharedMemoryAddressitself.DefaultNvlinkTransportPolicy::openIpcMemHandle(mooncake-transfer-engine/src/transport/nvlink_transport/nvlink_transport.cpp) callscudaIpcOpenMemHandle(address, handle, cudaIpcMemLazyEnablePeerAccess)with no device set.cudaIpcOpenMemHandlemaps into the calling thread's current context, so the mapping is bound to whatever device that thread happens to be on, which for a fresh thread is 0. Every later copy then runs a pointer that lives in GPU 0's context, hence the two hops you measured.The policy already knows how to pick the right device for a copy.
selectStreamDevicein the same class doesgetDeviceForPointer(local_buffer)and falls back to the current device, so the stream is on the source device while the mapping is not. And the MUSA policy already records where the handle was opened,shm_entry.device_id = ipc_device, guarded by#ifdef USE_MUSA; the CUDA policy passesopened_device = -1and explicitly says the recorded device is only needed by context-sensitive closes. So the concept is present and deliberately unconnected on the CUDA path.Two consequences for whoever fixes this, beyond setting the device before the open:
The cache key has to carry the device.
remap_entries_is keyed by(target_id, entry.addr). Once a handle is opened on a device it is only cheap from that device, so the same remote entry reached from two local devices needs two mappings and the key must distinguish them, otherwise the fix just moves the bug from "first thread wins" to "first device wins". Your--bindrun hides this because binding makes every open land on one device.Closing has to match.
cudaIpcCloseMemHandleis per mapping, so a device-aware cache needs a device-aware close, and the existingopened_deviceout-param is the natural place to carry it. This is where I want to flag a collision before anyone starts.Three open PRs are already restructuring exactly this function and its mapping lifetime, so I have deliberately not started an implementation here: #2380 fixes a VMM resource leak in
relocateSharedMemoryAddress, #4340 rewrites the relocation for suballocated buffers and adds arelocatepath overremap_entries_, and #4488 is its follow-up on IPC owner-device ordinals for HIP. None of them touches the calling-thread-device question above, so this bug is genuinely open and none of those three fixes it, but a device-aware cache key and a device-aware close land squarely on the lines they are editing. Whoever takes this should either build on top of #4340 or wait for that series to land.For the record on severity: the report's numbers are self-consistent and the shape is the right one. A 7x penalty on intra-node NVLink only when the first write to a peer is issued from a thread on another device is easy to miss in a benchmark that sets the device up front, which is why it survives this long.