Skip to content

[TransferEngine] Add NCCL host and device transport backend#2852

Open
akhillanger wants to merge 11 commits into
kvcache-ai:mainfrom
akhillanger:alanger/pr/te_nccl_host_transport
Open

[TransferEngine] Add NCCL host and device transport backend#2852
akhillanger wants to merge 11 commits into
kvcache-ai:mainfrom
akhillanger:alanger/pr/te_nccl_host_transport

Conversation

@akhillanger

@akhillanger akhillanger commented Jul 10, 2026

Copy link
Copy Markdown

Summary

This PR adds two opt-in experimental NCCL backends to Mooncake Transfer Engine:

  • A host-driven NCCL RMA transport implementing the existing Transport interface.
  • A CUDA device transport exposing NCCL LSA and GIN operations to kernels.

Host RMA transport

installTransport("nccl") installs a WRITE-only transport in a TransferEngine(false) instance. The transport uses the existing P2P metadata handshake to exchange an opaque NCCL bootstrap payload and registered CUDA-buffer metadata.

Each peer/device session creates a two-rank NCCL communicator, collectively registers corresponding buffers as symmetric NCCL windows, and submits transfers with ncclPutSignal. CUDA events report asynchronous completion through the normal Transfer Engine status API.

The host backend follows the classic Transfer Engine single-transport model:

  • NCCL must be installed before local buffers are registered.
  • NCCL must be the only transport in that engine instance; there is no routing or fallback to another host transport.
  • Both peers must register the same number of buffers in the same order with matching sizes.
  • Both endpoints must remain available while the first transfer initializes the session and collective windows.
  • READ requests are rejected with Status::NotSupportedTransport; NCCL 2.30 has no public host-side Get operation.

Device transport

getOrCreateNcclTransport() provides host-side setup for an NCCL device communicator. Applications exchange the generated NCCL unique ID through their existing control plane, initialize every rank collectively, and allocate/register symmetric buffers.

A compact NcclDeviceContext is passed by value to CUDA kernels. The device helpers provide:

  • Local, LSA, and GIN route discovery.
  • LSA peer-pointer access, multimem access, and LSA barriers.
  • GIN put, put-with-signal, signal-add, flush, signal-read, and signal-wait operations.
  • A lane-selected put call shape compatible with the existing IBGDA device API, allowing consumer-side template selection between backends.

NCCL-specific declarations live in transport/device/nccl_device_transport.h; the generic device transport API is unchanged.

Build

Both backends require CUDA and NCCL 2.30.4 or newer and are disabled by default.

cmake -S . -B build \
  -DUSE_CUDA=ON \
  -DUSE_NCCL_HOST=ON \
  -DUSE_NCCL_DEVICE=ON

cmake --build build -j

The options can be enabled independently:

  • USE_NCCL_HOST requires the public nccl.h host API.
  • USE_NCCL_DEVICE additionally requires the experimental nccl_device.h API.
  • NCCL_ROOT selects a nonstandard NCCL installation.

Optional manual examples are enabled with:

-DBUILD_NCCL_HOST_TRANSPORT_EXAMPLE=ON
-DBUILD_NCCL_DEVICE_TRANSPORT_EXAMPLE=ON

Device constraints

  • Device initialization and buffer registration are collective and must occur in the same order on every rank.
  • Host calls on one device transport require external serialization.
  • initialize() binds the transport to the current CUDA device; that device must remain current for later host calls, shutdown, and destruction.
  • Device code currently supports full-world GIN connectivity; rail-team connectivity is not included.
  • NCCL Device API headers used for Mooncake, AOT kernels, or JIT kernels must exactly match the runtime libnccl. Device code must be rebuilt or re-JITed after an NCCL upgrade.

Examples and tests

  • nccl_host_transport_example uses two GPUs to validate concurrent and bidirectional WRITE transfers and explicit READ rejection.
  • nccl_device_transport_example uses one process per rank to validate LSA or GIN put/signal completion from a device kernel.
  • transfer_metadata_test covers ordered NCCL GPU-buffer metadata and opaque handshake-payload round trips.
  • device_backend_api_compatibility_test instantiates one templated put operation for NCCL and IBGDA contexts.

Validation

  • Host transport, shared Transfer Engine changes, host example, and device host-side implementation compile in syntax-only checks.
  • Metadata and routing sources compile with normal and ENABLE_MULTI_PROTOCOL configurations.
  • Focused CMake probes pass for host-only/device-enabled dependency gating and NCCL versions with a zero patch component.
  • CUDA kernel compilation and GPU runtime tests were not run on the current x86-64 runner because its available CUDA/NCCL toolchain and dependencies are AArch64.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces NCCL-based transport backends to the Mooncake transfer engine, adding support for both device-initiated (USE_NCCL_DEVICE) and host-submitted RMA (USE_NCCL_HOST) communication paths, along with corresponding examples and handshake protocol integration. The code review identified several critical issues in the new host transport implementation: a state desynchronization risk in unregisterMemory if metadata updates fail, a potential GPU memory safety issue if cudaEventRecord fails after a successful transfer submission, and multiple potential crashes or exceptions due to unvalidated JSON parsing during handshake and buffer catalog decoding.

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.

Comment on lines +598 to +623
int unregisterMemory(void* addr, bool update_metadata) {
{
std::lock_guard<std::mutex> sessions_lock(sessions_mutex_);
if (!sessions_.empty() || !bootstrap_ids_.empty()) {
LOG(ERROR) << "[Host NCCL] cannot unregister memory while "
"NCCL sessions own collective windows";
return ERR_INVALID_ARGUMENT;
}
}

bool found = false;
{
std::lock_guard<std::mutex> lock(buffers_mutex_);
auto it = std::find_if(
local_buffers_.begin(), local_buffers_.end(),
[addr](const BufferInfo& buffer) {
return buffer.addr == reinterpret_cast<uint64_t>(addr);
});
if (it != local_buffers_.end()) {
local_buffers_.erase(it);
found = true;
}
}
if (!found) return ERR_ADDRESS_NOT_REGISTERED;
return metadata_->removeLocalMemoryBuffer(addr, update_metadata);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In unregisterMemory, the buffer is erased from local_buffers_ before calling metadata_->removeLocalMemoryBuffer. If removeLocalMemoryBuffer fails, the local state becomes desynchronized from the metadata store. We should only erase the buffer after a successful metadata update, or roll back the erasure on failure.

int unregisterMemory(void* addr, bool update_metadata) {
    {
        std::lock_guard<std::mutex> sessions_lock(sessions_mutex_);
        if (!sessions_.empty() || !bootstrap_ids_.empty()) {
            LOG(ERROR) << "[Host NCCL] cannot unregister memory while "
                          "NCCL sessions own collective windows";
            return ERR_INVALID_ARGUMENT;
        }
    }

    bool found = false;
    BufferInfo removed_info;
    {
        std::lock_guard<std::mutex> lock(buffers_mutex_);
        auto it = std::find_if(
            local_buffers_.begin(), local_buffers_.end(),
            [addr](const BufferInfo& buffer) {
                return buffer.addr == reinterpret_cast<uint64_t>(addr);
            });
        if (it != local_buffers_.end()) {
            removed_info = *it;
            local_buffers_.erase(it);
            found = true;
        }
    }
    if (!found) return ERR_ADDRESS_NOT_REGISTERED;
    int result = metadata_->removeLocalMemoryBuffer(addr, update_metadata);
    if (result != 0) {
        std::lock_guard<std::mutex> lock(buffers_mutex_);
        local_buffers_.push_back(removed_info);
    }
    return result;

@akhillanger akhillanger Jul 10, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 9f3cdfb. unregisterMemoryLocked now performs the protocol-scoped metadata removal before erasing the entry from local_buffers_, while the caller holds buffers_mutex_. The metadata side stages a cloned descriptor, publishes it first when requested, and commits the local metadata cache only after publication succeeds, so a lookup or publication failure leaves both catalogs unchanged. Batch unregister also prevalidates every address and uses one bulk transaction, making it all-or-nothing. I added coverage for failed atomic bulk removal, successful bulk removal, and same-address protocol-scoped removal.

Disclosure: This response was AI-generated.

Comment on lines +251 to +257
ncclResult_t result = ncclPutSignal(
source, length, ncclUint8, owner_rank, window->handle,
dest_addr - destination.addr, 0, 0, 0, comm_, stream_);
if (result == ncclSuccess && event) {
cuda_result = cudaEventRecord(event, stream_);
}
if (saved_device >= 0) cudaSetDevice(saved_device);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If ncclPutSignal succeeds but cudaEventRecord fails, the transfer is already submitted to the GPU stream, but the function returns an error. The caller might immediately free or overwrite the source buffer, leading to silent data corruption or use-after-free on the GPU. We should synchronously wait for the stream to finish if cudaEventRecord fails to ensure safety.

        ncclResult_t result = ncclPutSignal(
            source, length, ncclUint8, owner_rank, window->handle,
            dest_addr - destination.addr, 0, 0, 0, comm_, stream_);
        if (result == ncclSuccess && event) {
            cuda_result = cudaEventRecord(event, stream_);
            if (cuda_result != cudaSuccess) {
                cudaStreamSynchronize(stream_);
            }
        }
        if (saved_device >= 0) cudaSetDevice(saved_device);

@akhillanger akhillanger Jul 10, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 9f3cdfb. After a successful ncclPutSignal, a cudaEventRecord failure now synchronizes stream_ before returning the error. This drains the already-queued put before the caller can reuse or free its source buffer. The original event-record error is preserved; if the fallback synchronization also fails, that error is appended and the session is marked failed. The normal WRITE and reverse-PUT READ paths passed the two-GPU and forced-network GB200 tests.

Disclosure: This response was AI-generated.

Comment on lines +942 to +953
int onHandshake(const HandShakeDesc& peer_desc,
HandShakeDesc& local_desc) {
local_desc.protocol = kHandshakeProtocol;
Json::Value request;
std::string error;
if (!decodeJson(peer_desc.payload, &request, &error)) {
local_desc.reply_msg = "Invalid NCCL handshake payload: " + error;
return 0;
}

const std::string op = request["op"].asString();
Json::Value response;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Accessing request["op"].asString() without validating that request is an object and contains a string "op" can cause a crash or throw a Json::LogicError if the peer sends a malformed payload. We should validate the JSON structure before accessing its fields.

    int onHandshake(const HandShakeDesc& peer_desc,
                    HandShakeDesc& local_desc) {
        local_desc.protocol = kHandshakeProtocol;
        Json::Value request;
        std::string error;
        if (!decodeJson(peer_desc.payload, &request, &error)) {
            local_desc.reply_msg = "Invalid NCCL handshake payload: " + error;
            return 0;
        }

        if (!request.isObject() || !request.isMember("op") || !request["op"].isString()) {
            local_desc.reply_msg = "Missing or invalid 'op' field in handshake request";
            return 0;
        }

        const std::string op = request["op"].asString();

@akhillanger akhillanger Jul 10, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 9f3cdfb. decodeJson now uses strict parsing and rejects any non-object root. onHandshake then requires op to exist and be a string before calling asString(), and the dispatch is protected by a Json::Exception boundary so malformed peer input is returned as a handshake rejection rather than escaping the daemon callback. The outer transfer-handshake decoder now also rejects wrong-typed present fields.

Disclosure: This response was AI-generated.

Comment on lines +113 to +125
buffers->clear();
for (const auto& item : value) {
BufferInfo buffer;
buffer.addr = item["addr"].asUInt64();
buffer.length = item["length"].asUInt64();
buffer.device_id = item["device_id"].asInt();
if (!buffer.addr || !buffer.length ||
buffer.device_id != expected_device) {
if (error) *error = "Invalid NCCL buffer catalog entry";
return false;
}
buffers->push_back(buffer);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Calling asUInt64() and asInt() on item fields without verifying their existence and types can cause crashes or exceptions if the peer sends a malformed buffer catalog. We should validate each item's structure.

    buffers->clear();
    for (const auto& item : value) {
        if (!item.isObject() || !item.isMember("addr") || !item["addr"].isUInt64() ||
            !item.isMember("length") || !item["length"].isUInt64() ||
            !item.isMember("device_id") || !item["device_id"].isInt()) {
            if (error) *error = "Malformed buffer catalog entry";
            return false;
        }
        BufferInfo buffer;
        buffer.addr = item["addr"].asUInt64();
        buffer.length = item["length"].asUInt64();
        buffer.device_id = item["device_id"].asInt();
        if (!buffer.addr || !buffer.length ||
            buffer.device_id != expected_device) {
            if (error) *error = "Invalid NCCL buffer catalog entry";
            return false;
        }
        buffers->push_back(buffer);
    }

@akhillanger akhillanger Jul 10, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 9f3cdfb. decodeBuffers now validates that the catalog is an array and that every entry is an object with UInt64 address/length fields and an integer device ID before conversion. It also rejects zero values, address-range overflow, the wrong device, empty catalogs, and duplicate or overlapping ranges. Decoding uses a temporary vector and only replaces the output after the entire catalog validates, so a malformed later entry cannot leave a partial result.

Disclosure: This response was AI-generated.

@xiaofanl-nvidia

Copy link
Copy Markdown

++ @UNIDY2002 @staryxchen from the RFC #2630

@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Jul 10, 2026
@codecov-commenter

codecov-commenter commented Jul 10, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 96.36364% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
mooncake-transfer-engine/src/transfer_metadata.cpp 88.46% 3 Missing ⚠️
...oncake-transfer-engine/include/transfer_metadata.h 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@UNIDY2002
UNIDY2002 self-requested a review July 11, 2026 15:32

@UNIDY2002 UNIDY2002 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution! I have a few comments that should be addressed before we move on.

Comment thread mooncake-common/common.cmake

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be a compile-time probe. I'm wondering whether it's necessary, since the regular build process should already reveal whether these features are supported.

@akhillanger akhillanger Jul 13, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept this compile probe because the regular library target does not instantiate the header-only device helpers. The CUDA example has two separate opt-in gates—BUILD_EXAMPLES=ON and BUILD_NCCL_DEVICE_TRANSPORT_EXAMPLE=ON, with the latter disabled by default—so it is not compiled by a normal USE_NCCL_DEVICE=ON build. The probe provides CI coverage that instantiates the NCCL and IBGDA APIs from the same consumer template without requiring runtime GPU or NIC hardware. I added comments explaining this distinction.

Disclosure: This response was generated with help of AI.

Comment thread mooncake-transfer-engine/tests/transfer_metadata_test.cpp Outdated
Comment thread mooncake-transfer-engine/src/transfer_metadata.cpp Outdated
Comment thread mooncake-transfer-engine/src/transfer_metadata.cpp Outdated
Comment thread mooncake-transfer-engine/src/transfer_metadata.cpp
Comment thread mooncake-transfer-engine/src/transfer_engine_impl.cpp Outdated
Comment thread mooncake-transfer-engine/src/transfer_metadata.cpp
Comment thread mooncake-transfer-engine/src/transfer_metadata.cpp Outdated

@staryxchen staryxchen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for contribution!

I have a question: The PR description says "A TE read maps to ncclGet", but the actual READ implementation uses requestReversePut — a synchronous handshake to the peer, which then performs the reverse PUT via handleReversePut → putAndWait. But there's no ncclGet call in the code.

More importantly, this puts a synchronous RPC on the data path. In the RFC #2630 discussion, a cross-node RPC on the data path was called out as one of the core issues with the POC, which motivated the control-plane/data-plane separation principle. Additionally, handleReversePut runs cudaEventSynchronize on the handshake daemon thread, blocking all other peers' handshakes on that thread.

@akhillanger

akhillanger commented Jul 13, 2026

Copy link
Copy Markdown
Author

@staryxchen thanks for the review:

You are correct. There was a discrepancy in PR description. Also, I removed the reversed-PUT implementation. Currently NCCL does not have a host-side ncclGet. The host backend is therefore explicitly WRITE-only for now.

When a submitted task's opcode is READ, submitTasks() creates its status-tracking slice, rejects it locally, marks it failed, and returns NotSupportedTransport before that request performs metadata lookup, NCCL session lookup/creation, or peer bootstrap. Thus, a READ request cannot initiate or use an NCCL session, although a session created independently by an earlier or concurrent WRITE may already exist. Mixed-protocol routing can fall through to another READ-capable backend. The examples and documentation were updated accordingly, and the PR description has been corrected as well.

@UNIDY2002 UNIDY2002 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution.

My main concern is that this PR is not only adding an NCCL transport. It also introduces generic framework changes: protocol-scoped metadata removal, protocol-dispatched handshake handlers, default-handler replacement, and new local metadata transaction behavior.

Those changes seem motivated by a broader multi-transport coexistence model. That model is not part of the current TE host-transport contract, and I do not think we should expand TE in that direction in this PR. In the current patch, the protocol-scoped remove APIs are only used by NCCL, while existing transports still use the old address-only path, which makes the framework change look partial and NCCL-specific rather than a coherent transport-layer design.

Please keep this PR focused on the NCCL backend within the existing TE model. If the goal is to support richer multi-transport coexistence, protocol-dispatched handshakes, or per-transport registration/removal semantics, that work should be developed under the TENT framework instead. I would be happy to review and help align that direction there, but I do not think these framework changes should be introduced through this NCCL TE transport PR.

Comment thread mooncake-transfer-engine/src/transfer_metadata.cpp Outdated
Comment thread mooncake-transfer-engine/include/transfer_metadata.h Outdated
Comment thread mooncake-transfer-engine/src/transfer_metadata.cpp

@alogfans alogfans left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR should be refactored to minimize the change of existing modules.

@akhillanger

Copy link
Copy Markdown
Author

Thank you for the comments @UNIDY2002 and @alogfans. I will work on addressing your concerns and get back.

@UNIDY2002 UNIDY2002 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code is much cleaner now.

LGTM on the device transport part.

@staryxchen @alogfans When you have a chance, could you please take a look at the host transport part as well? Thanks!

cudaSetDevice(local_device);
cudaEvent_t event = nullptr;
cudaError_t cuda_result =
cudaEventCreateWithFlags(&event, cudaEventDisableTiming);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each WRITE slice allocates a cudaEvent_t into slice->nccl.event, and it is only destroyed inside poll() on success or error. But TransferTask::~TransferTask() (transport.h) deallocates slices via the cache without any transport-side cleanup, so a batch destroyed before every POSTED slice has been polled to completion leaks the live CUDA events.

NCCL is the first TE transport that owns a per-slice CUDA handle requiring explicit destruction, so the existing slice lifecycle does not cover this. Could you add a cleanup path — e.g. a transport hook invoked on slice deallocation, or track in-flight events in Impl and destroy them in ~Impl()?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 16d1a19. I added a one-shot cleanup callback to each slice, invoked by ThreadLocalSliceCache::deallocate() before the slice is deleted or cached. NCCL installs a callback that destroys any remaining cudaEvent_t on the correct CUDA device.

Submission-error and polling-completion paths now use the same idempotent helper. The event handle and callback are cleared before cleanup or cache reuse, preventing both double destruction and accidental inheritance by another transport. I also added tests covering cleanup through TransferTask::~TransferTask() and callback clearing across slice-cache reuse.

return true;
}

std::shared_ptr<NcclSession> getOrCreateSession(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once a session enters sessions_, it is only removed in ~Impl. If initialize() fails (failed_ = true), waitReady returns nullptr for every subsequent transfer to that peer, and the session cannot be recreated because buffers_frozen_ is already latched — the only recovery is destroying the whole transport.

For an experimental backend this may be acceptable, but could you at least document it in "Device constraints", or evict failed sessions so a later transfer can retry bootstrap?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 16d1a19 using the documentation option. The public NCCL host contract and build constraints now state that the first valid WRITE freezes the registered-buffer catalog, a terminal session failure remains cached for that endpoint/device pair, and subsequent transfers do not retry bootstrap. Recovery requires recreating the NCCL-only TransferEngine on both peers and registering the buffers again.

return attributes.device;
}

std::string makeSessionKey(const std::string& local_name, int local_device,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makeSessionKey orders by local_name < peer_name, and local_rank is assigned the same way (local_server_name_ < peer_name ? 0 : 1). When local_name == peer_name (same-node cross-device), both sides take the else branch with swapped arguments, producing different keys; and both compute rank = 1, so neither creates the unique ID — bootstrap deadlocks.

Same-node transfers should use NVLink, so this is likely out of scope, but the latent bug is there. If you want it correct, compare the (name, device) tuple instead of name alone:

if (std::tie(local_name, local_device) < std::tie(peer_name, peer_device)) {
    ...
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 16d1a19. Session canonicalization now compares the complete (server name, CUDA device) tuple, and the same comparator is used for makeSessionKey() and both outbound and inbound rank-election paths.
At the ordering layer, equal server names on different devices now produce the same canonical key in both directions, complementary ranks, and exactly one rank-0 unique-ID creator. Same-engine transfers remain intentionally rejected and should use the intra-node NVLink/P2P path; this fixes the latent ordering assumption without claiming same-engine NCCL support.

@UNIDY2002

Copy link
Copy Markdown
Collaborator

@akhillanger Hi! Would you help resolve the conflicts with the main branch, so that the CI could be triggered? Thanks.

Akhil Langer and others added 5 commits July 16, 2026 22:56
Hide NCCL communicators and windows behind opaque Mooncake contexts, add lane-defined LSA/GIN helpers and capability routing, and provide collective-safe buffer allocation. Align NCCL 2.30.4 CMake discovery, CXI guards, lifecycle semantics, and run-scoped example bootstrap.
Make buffer catalog freezing and metadata removal atomic, drain queued puts when event recording fails, and strictly validate NCCL handshake payloads.
Make kernel operations intentionally unchecked, shrink the by-value context, add compile-only NCCL/IBGDA traits coverage, and document the exact NCCL device-code version contract.
@akhillanger
akhillanger force-pushed the alanger/pr/te_nccl_host_transport branch from 16d1a19 to 5c8362a Compare July 17, 2026 06:07
@UNIDY2002

Copy link
Copy Markdown
Collaborator

Device code currently supports full-world GIN connectivity; rail-team connectivity is not included.

Kernels such as DeepEP V2 require rail-team connectivity. Is there any plan or interest to support this feature in the future? @akhillanger

@akhillanger

Copy link
Copy Markdown
Author

Good point. DeepEP V2's hybrid path does require rail connectivity: it checks railedGinType, requests NCCL_GIN_CONNECTION_RAIL, and uses ncclTeamTagRail for the inter-node phase. DeepEP also retains a direct mode using full world connectivity, so this requirement applies specifically to its hierarchical hybrid path rather than every V2 kernel.

This PR implements only full world-team GIN connectivity. We can support rail connectivity as a follow-up so this initial backend remains focused.

@staryxchen staryxchen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, only a few nits

@@ -0,0 +1,338 @@
// Copyright 2024 KVCache.AI

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Copyright 2024 KVCache.AI
// Copyright 2026 KVCache.AI

@@ -0,0 +1,302 @@
// Copyright 2024 KVCache.AI

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Copyright 2024 KVCache.AI
// Copyright 2026 KVCache.AI

@@ -0,0 +1,545 @@
// Copyright 2024 KVCache.AI

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Copyright 2024 KVCache.AI
// Copyright 2026 KVCache.AI

@@ -0,0 +1,80 @@
// Copyright 2024 KVCache.AI

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Copyright 2024 KVCache.AI
// Copyright 2026 KVCache.AI

@akhillanger

Copy link
Copy Markdown
Author

@staryxchen fixed the copyright issues you pointed. Thanks.

@staryxchen staryxchen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@UNIDY2002 UNIDY2002 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Let's wait for the CI results.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Common documentation Improvements or additions to documentation run-ci Transfer Engine

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants