Repository navigation
[1/3] [TransferEngine] Add HOST_NUMA Fabric allocation core - #2966
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces NVLink Virtual Memory Management (VMM) allocation support and Fabric memory transport capabilities to the Mooncake transfer engine, refactoring memory lifecycle management in NvlinkTransport and adding comprehensive tests. The review feedback highlights critical safety and performance improvements, including addressing potential null pointer dereferences when retrieving the local segment descriptor in TransferMetadata, and optimizing driver overhead by batching cuMemSetAccess calls rather than executing them in a loop for each device.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
4aef80a to
ae700b3
Compare
he-yufeng
left a comment
There was a problem hiding this comment.
Thanks for this — the VMM lifecycle handling here is careful work. A few things I want to call out and two points worth a closer look before merge. I focused the review on the new NvlinkVmmAllocation lifecycle in nvlink_vmm_allocation.cpp.
What's done well. The reverse-order Release() with per-stage commit and retained ownership on failure is the right shape for CUDA VMM teardown, and pre-allocating the intrusive CleanupPendingVmmOwnerNode before acquiring any CUDA resource (so recording a rollback can never itself fail under memory pressure) is a nice touch. Moving the owned-range provenance removal to the very start of Release(), before the first unmap, so a stuck teardown stops satisfying IsExactOwnedRange rather than staying eligible for a fresh remote registration, is exactly the safe direction.
1. A single stuck teardown can silently wedge the whole allocation path (availability). CreateWithDriverApi opens with:
if (!RetryCleanupPendingOwners()) {
return Status::Memory("a previous VMM factory rollback is still pending cleanup");
}If any quarantined owner keeps failing Release() (driver persistently rejecting a teardown stage), every subsequent Create fails from here on, process-wide, with no upper bound and no escape valve. The retention itself is defensible, but the failure is invisible to operators: CleanupPendingOwnerCount() exists but isn't wired to any metric or log-once, so the symptom presents as "all Fabric VMM allocations suddenly fail" with the root cause buried. Could we at least surface the pending count (a gauge, or a throttled LOG(ERROR) naming the retained range) so this degraded state is observable? Whether a bounded-retry-then-give-up policy is acceptable is a product call, but the observability gap seems worth closing regardless.
2. Rollback re-releases a handle whose first cuMemRelease returned an error (worth confirming). In the success path the handle is released once and the flags cleared:
result = api.mem_release(handle);
if (result != CUDA_SUCCESS) {
status = cudaDriverFailure("cuMemRelease", result);
return rollback(status); // -> owner->Release() -> mem_release(handle) again
}
owner->handle_owned_ = false;When that mem_release fails, handle_owned_ is still true, so the rollback path's Release() calls cuMemRelease on the same handle a second time. This is only safe if a failing cuMemRelease is guaranteed to be atomic (no state change on error), so the retry acts on a still-valid handle rather than double-releasing one that was partially torn down. I believe that's the driver's contract, but given this is the one spot where the same handle can be released twice, it'd be good to either confirm that assumption explicitly or clear handle_owned_ before entering rollback here and lean on the quarantine path if the handle truly leaked. Same reasoning applies to the mid-Create failures that reach rollback while mapped_/address_reserved_ are set — those are fine because each stage's flag gates its own teardown, but the handle stage is the asymmetric one.
Neither of these blocks the design, which I think is sound. #2 is a small correctness clarification; #1 is the one I'd most like addressed since a wedged allocation path is hard to diagnose in production. Happy to look again once you've had a chance to weigh in.
|
@he-yufeng Thanks for the detailed lifecycle review. Addressed both points in 5e06423.
Also addressed the two batching comments: provider allocation and consumer lazy import now build complete access descriptor arrays before acquiring CUDA ownership, then issue one cuMemSetAccess call. Unrelated whole-header formatting churn was reverted. Validation for head 5e06423: clang-format 20 passed, and both ARM64 Python 3.10/3.12 jobs passed Build project plus Build and gate NVLink VMM unit tests in https://github.com/neverhook/Mooncake/actions/runs/29802040245. The broader manually triggered matrix is still finishing, but the two NVLink gates are complete. |
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
731423f to
dbdbe73
Compare
8ef3099 to
3c85060
Compare
|
Current version summary (
Scope remains the minimum Transfer Engine prerequisite for #2914. Store EGM pool integration, the complete Provider/Consumer path, and GB200/NVL72 end-to-end validation stay in the follow-up Store-only PR and are not merge gates for #2966. |
40d3b03 to
348eb18
Compare
|
Stack rebase update:
Exact-head GitHub Actions are now the remaining automated gate. GB200/NVL72 integration remains a later Store-stack gate, not a gate for #2966. |
348eb18 to
8558c36
Compare
|
Stack maintenance update:
GB200 was not rerun because this was a patch-equivalent baseline rebase with no functional-path change. |
8558c36 to
c55ae61
Compare
|
Review and stack update:
|
c55ae61 to
1a54621
Compare
|
Rebased the stack on current main. The HOST_NUMA and registration patch is unchanged. Focused tests pass; the fresh CI run is still finishing. |
1a54621 to
5682523
Compare
|
Rebased on current main, including the SSD-test fix in #3870. The HOST_NUMA patch is unchanged. Fresh CI has been triggered for this head. |
5682523 to
e699484
Compare
|
The stack is rebased on current main. I kept the new CI layout and wired the focused NVLink tests into its path filter; the allocation and registration code did not need changes. |
|
PR#3978 is merged, does this overlap with your changes? |
staryxchen
left a comment
There was a problem hiding this comment.
Overall the approach looks right. Left a few inline comments, please take a look.
|
The new Replaying the filter over the last 150 first-parent merges on
The list ( What it pays is a full-repo configure + build ( Suggestion: drop the dedicated build rather than tune the filter. The One prerequisite: If you would rather keep the dedicated step, narrowing the filter alone also fixes the trigger rate: |
e699484 to
3374985
Compare
3374985 to
c0a1a22
Compare
|
Thanks @Aionw, I went with the shared build. The fake-driver cases now run in |
staryxchen
left a comment
There was a problem hiding this comment.
Looks good from TE side
Summary
This PR provides the minimum Transfer Engine prerequisite for RFC #2914.
It adds CUDA
HOST_NUMAFabric allocation and closes the exact-range NVLinkregistration lifecycle. It intentionally does not add the Store EGM pool or
the complete Provider/Consumer data path.
Stack
This is [1/3] of the RFC implementation stack:
The fork branches form this exact commit chain. GitHub still displays each
community PR against
mainbecause a cross-fork PR cannot use another forkbranch as its base.
Core changes
HOST_NUMA placement
NvlinkHostNumaAllocationfor Fabric-capable CUDAHOST_NUMAmemory.VMM capabilities independently from NUMA discovery.
discovered node's granularity.
unrelated node cannot block the override.
cuMemSetAccesscall.Retryable exact-range registration lifecycle
allocation handle. A duplicate or map-allocation failure therefore cannot
leave an untracked retained handle.
cuMemRetainAllocationHandle()fails, returnsERR_MEMORY, removes theplaceholder, and does not publish metadata.
lock-held cleanup helper. A failed registration can be retried at the same
address: pending cleanup finishes before a new handle is acquired, while a
successful registration still rejects duplicates.
rollback, and deferred/batch cleanup.
Release()refuses to unmap a pinnedrange. A range being released remains unavailable for registration until
address-free succeeds.
partial cleanup retries skip already completed work.
later retry does not repeat a completed delete.
ERR_ADDRESS_NOT_REGISTEREDremains idempotent.
cuMemRelease()preserves the registration record and handleownership; a later unregister retries only the remaining release stage.
after a successful release, and a failed call can be retried.
Create()requires an empty output. An error may still return a partialowner; the caller must retain it and retry
Release(). Destruction remainsbest-effort, not a replacement for successful explicit cleanup.
CUDA documents that these APIs may report errors from earlier asynchronous
work and does not explicitly guarantee failure atomicity. This PR deliberately
treats a non-successful release as retaining ownership so cleanup remains
retryable. That policy and its accepted ambiguity are documented in the code.
See the CUDA Driver API virtual-memory documentation.
Lifetime and tests
OwnedRangeRegistrya process-lifetime heap singleton to avoid staticdestruction-order hazards.
retain/export/publication failures, metadata retry, idempotent unregister,
release retry, concurrent register/release, VA reuse, and partial batch cleanup.
CI integration
unit-testsbuildwith
USE_MNNVL=ON, reusing the CUDA driver runtime setup.nvlink_gpuand excludes only that entry from theCPU runner. Hardware and fake-driver cases use separate
GTEST_FILTERenvironments, so they do not run twice.
its existing test selection when the input is omitted.
Scope boundary
This PR does not change Store behavior, Consumer mapping/cache, wire schema,
TransferMetadatabehavior, RDMA behavior, or Store configuration. Store EGM pooling,the full Provider/Consumer path, and GB200/NVL72 integration validation belong
to the follow-up Store layer. GB200 validation is therefore not a merge gate
for #2966.
Review size
Validation
This revision consolidates the CI build and test selection; allocation,
registration, transfer, and cleanup behavior are unchanged.
git diff --checkand all applicable touched-file pre-commit hooks.nvlink_vmm_unit(2 tests).fake-driver entries remain selected and ran successfully.
unit-testssweep and GitHub Actions CI Gate.Local CUDA-on/off regression tests passed on the composed stack (#3431).
GitHub Actions passed the complete CTest sweep (226 entries) and CI Gate
for this revision. Both fake-driver entries ran successfully; the hardware-only
entry was excluded on the CPU runner.
Repository-wide
pre-commit run --all-filesis not a merge gate.Related RFC: #2914
Module
mooncake-transfer-engine)Type of Change
Checklist
AI Assistance Disclosure
Codex assisted with the rebase, focused lifecycle fixes and tests, scoped
validation, and PR preparation. The human submitter remains responsible for
reviewing, understanding, and defending every changed line.