mirror of
https://github.com/kvcache-ai/Mooncake.git
synced 2026-11-05 01:17:55 +00:00
[GH-ISSUE #4979] [Bug][TE]: nvlink_intra — transfer_*_on_cuda always kills the process; on hosts without GPU P2P a mixed batch fails silently for its valid entries #1261
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#1261
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 @0z5a on GitHub (Oct 27, 2026).
Original GitHub issue: https://github.com/kvcache-ai/Mooncake/issues/4979
Environment: 2× NVIDIA L40S (PCIe, P2P disabled:
nvidia-smi topo -p2p r= NS between the GPUs), driver 580, CUDA 13.3, x86 nightly wheelmooncake-transfer-engine-cuda13 0.3.14.dev20261025(maind53ddb9f),MC_INTRANODE_NVLINK=1, P2PHANDSHAKE, one initiator process and one target process.A.
transfer_{write,read}_on_cuda+nvlink_intra→_exit(1), even on a single GPUSame-GPU write (target buffer and source on GPU 0, no P2P involved):
then the process exits with status 1 (by design of
transfer_on_cuda_callback's error path). The plaintransfer_sync_writeof the same buffers succeeds.Cause (main):
submitTransferruns inside thecudaLaunchHostFunccallback.IntraNodeNvlinkTransportgets its copy stream and event from thread-local pools (PerDeviceStreamPool::getOrCreate,tl_device_stream_pool/tl_device_event_pool). The callback runs on the CUDA driver's callback thread, whose pools are always empty, so every call lazily runscudaSetDevice+cudaStreamCreateWithFlagsinside the host function, which CUDA forbids (host functions must not call CUDA APIs). The latercudaEventRecord/cudaEventSynchronizeinsubmitTransferTaskwould hit the same restriction. So this combination cannot work on any hardware, NVLink or not.B. No P2P: misleading error, valid entries dropped, batch leaked
cudaIpcOpenMemHandle failed: peer access is not supported between these two devices, but the caller getsMemory: device memory not registered.batch_transfer_sync_write/read: rc = -1, the valid same-GPU entry is not copied (destination stays all zero), andmulti_transport.cpp: Batch ... is still busy; cleanup has been deferred.submitTransferTaskreturns in the middle of the loop after earlier slices were created but beforesubmitBatchMemcpy.enableP2PAccess()exists but its only call site is commented out.Expected: check
cudaDeviceCanAccessPeerup front and either fall back (staged copy / another transport) or reject the whole batch before creating any slice, with an error that names the real cause.Results (one case per process, 40 s watchdog)
transfer_write_on_cuda_exit(1)transfer_write_on_cuda_exit(1)Related
Open PR #4919 (VMM buffers for
nvlink_intra) edits the same file but does not touch the thread-local stream/event pools,submitTransferTask's early return, orenableP2PAccess, so these findings are independent of it. (Open-PR search for on_cuda / nvlink_intra P2P / cuda IPC found nothing else.)AI assistance disclosure
Found by AI-assisted code reading (Claude) and confirmed with test scripts on the hardware listed above.
@github-actions[bot] commented on GitHub (Oct 27, 2026):
Thanks for opening this issue, @0z5a!
192209249A maintainer will triage this when possible. To help us respond faster, please include:
Useful links: Documentation · Contributing guide
@arun-elr commented on GitHub (Oct 28, 2026):
looked at A. agree it can't work as is: inside the
cudaLaunchHostFunccallback, nvlink_intra's submit creates streams/events, callscudaEventRecord/cudaEventSynchronizeandcudaMemcpyBatchAsync, and the status poll callscudaStreamQuery. a host function isn't allowed to make any of those calls, so pre-creating the thread-local pools wouldn't fix it.two options I see:
transfer_*_on_cudaraises in Python up front when the target goes through nvlink_intra, instead of_exit(1)from the callback. small change.cudaMemcpyBatchAsyncon that stream). that's what on_cuda should mean for a GPU-to-GPU transport, and it drops the host round trip. needs a transport hook, e.g.submitTransferOnStream(batch, entries, stream)that returns NotSupported by default.which do you prefer? sending B (one bad entry drops the rest) as its own PR first.
@he-yufeng commented on GitHub (Oct 29, 2026):
Both parts of this are covered by two PRs that are already open, so nobody needs to start a third.
Part A, CUDA API use from inside the
cudaLaunchHostFunccallback, is #5038. The on-CUDA entry points reject annvlink_intratarget before allocating a batch or registering the host function, so the caller gets an explicit error saying why a CUDA host function cannot call CUDA APIs instead of the driver callback killing the process with_exit(1). Your point about the thread-local stream and event pools being empty on the driver's callback thread is exactly the diagnosis I worked from.Part B, the no-P2P behavior, is #5022. Both NVLink submission entry points resolve every destination before publishing any slice, so a later IPC mapping failure returns an error without leaving earlier tasks holding pending slices that keep batch cleanup deferred. The error now names CUDA IPC peer access and buffer registration instead of the generic "device memory not registered".
Between them that covers the two failure rows in your table: the
transfer_write_on_cudaprocess exit and the mixed batch where a valid same-GPU entry was dropped and the batch leaked.One boundary both PRs state and I want to keep visible rather than let it read as done: neither enables P2P nor adds a fallback transport. Your expected behavior offered two options, fall back or reject the whole batch before creating any slice, and what landed is the second. Checking
cudaDeviceCanAccessPeerup front and rejecting cleanly is the part that stops the data loss and the leak; a staged-copy fallback or a transport switchover is a larger change and is not in either PR.#4104 and #4919 are unrelated to these two paths, and my search did not find a third PR touching the thread-local pools or
submitTransferTask's early return.@arun-elr commented on GitHub (Oct 29, 2026):
thanks, I missed #5022 when I posted. fine with both, so I'll drop my branch:
transfer_*_on_cudakilling the process from the host callback): #5038, failing fast on annvlink_intratarget.I have a two-process test that runs mixed batches (good+bad, bad+good, good+good) through the Python API on a real GPU. happy to run it against #5022 and #5038 on an H200 and post the results if that's useful.