Skip to content

support gpu direct rdma - #3144

Open
randomkang wants to merge 12 commits into
apache:masterfrom
randomkang:gdr
Open

randomkang wants to merge 12 commits into
apache:masterfrom
randomkang:gdr

Conversation

@randomkang

@randomkang randomkang commented Nov 9, 2025 •

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: resolve #3102

Problem Summary:

What is changed and the side effects?

Changed:

  1. recv all data on gpu first
  2. the gpu block is alloced from a gpu block pool
  3. brpc header, meta and body will be copied from gpu to cpu to process.
  4. To decrease the d2h counts, we will prefetch 512B to memory

Side effects:

  • Performance effects:

  • Breaking backward compatibility:


Check List:

Copilot AI 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.

Pull Request Overview

This PR adds GPU Direct RDMA (GDR) support to the BRPC framework, enabling efficient data transfer between GPU memory and RDMA-capable network devices. The implementation includes GPU memory pool management, CUDA stream pooling for asynchronous memory operations, and protocol-level changes to handle GPU-resident data.

  • Added GPU memory block pool allocator with configurable block sizes
  • Implemented CUDA stream pooling for optimized device-to-host and device-to-device transfers
  • Integrated GDR support into IOBuf, RDMA endpoint, and RPC protocol parsing

Reviewed Changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/butil/iobuf.h Added GPU-specific copy and cut methods with conditional compilation guards
src/butil/iobuf.cpp Implemented cutn_from_gpu and copy_from_gpu methods for GPU memory operations
src/butil/gpu/gpu_block_pool.h New header defining GPU memory pool allocator, stream pool, and region management
src/butil/gpu/gpu_block_pool.cpp Implementation of GPU memory allocation, CUDA stream management, and memory copy operations
src/brpc/rdma/rdma_helper.h Added function declaration for GPU index retrieval
src/brpc/rdma/rdma_helper.cpp Added GPU index configuration and initialization logic for GDR block pool
src/brpc/rdma/rdma_endpoint.h Added remote receive window tracking and GDR-specific receive posting method
src/brpc/rdma/rdma_endpoint.cpp Modified flow control, completion handling, and receive buffer posting for GDR support
src/brpc/policy/baidu_rpc_protocol.cpp Enhanced message parsing to detect and handle GPU memory with optimized prefetch
bazel/config/BUILD.bazel Added build configuration for GDR feature flag
BUILD.bazel Added CUDA dependencies and GPU block pool source to build

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/butil/iobuf.cpp Resolved
Comment thread src/brpc/rdma/rdma_endpoint.cpp Resolved
Comment thread src/brpc/rdma/rdma_endpoint.cpp Resolved
Comment thread src/brpc/rdma/rdma_helper.cpp Resolved
Comment thread src/butil/gpu/gpu_block_pool.h Resolved
Comment thread src/butil/iobuf.cpp Outdated
Comment on lines +763 to +764
clock_gettime(CLOCK_MONOTONIC, &end);
double time_us = (end.tv_sec - start.tv_sec) * 1e6 + (end.tv_nsec - start.tv_nsec) / 1e3;

Copilot AI Nov 9, 2025

Copy link

Choose a reason for hiding this comment

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

The variables start, end, and time_us are defined and computed but the result is only used in a commented-out log statement (lines 766-767). Consider removing this timing code if it's not being used, or uncomment the logging if it's needed for debugging.

Copilot uses AI. Check for mistakes.
Comment thread src/butil/iobuf.cpp Outdated
Comment on lines +1418 to +1422
double time_us = (end.tv_sec - start.tv_sec) * 1e6 + (end.tv_nsec - start.tv_nsec) / 1e3;
size_t copied_bytes = n - m;

// LOG(INFO) << "GDRCopy: " << copied_bytes << " bytes, "
// << time_us << " us" << ", to_gpu " << to_gpu;

Copilot AI Nov 9, 2025

Copy link

Choose a reason for hiding this comment

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

The variables time_us and copied_bytes are computed but only used in commented-out log statements (lines 1421-1422). Consider removing this unused code or uncommenting the logging if it's needed for debugging.

Suggested change
double time_us = (end.tv_sec - start.tv_sec) * 1e6 + (end.tv_nsec - start.tv_nsec) / 1e3;
size_t copied_bytes = n - m;
// LOG(INFO) << "GDRCopy: " << copied_bytes << " bytes, "
// << time_us << " us" << ", to_gpu " << to_gpu;

Copilot uses AI. Check for mistakes.
Comment thread src/butil/gpu/gpu_block_pool.cpp Resolved
Comment thread src/butil/iobuf.cpp Outdated
Comment on lines +1394 to +1395
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);

Copilot AI Nov 9, 2025

Copy link

Choose a reason for hiding this comment

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

The timing variables start and end are declared and used but the computed time_us value is only for commented-out logging. If timing is not actively needed, consider removing this instrumentation code.

Copilot uses AI. Check for mistakes.
Comment thread src/butil/iobuf.cpp Outdated
Comment on lines +744 to +745
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);

Copilot AI Nov 9, 2025

Copy link

Choose a reason for hiding this comment

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

The timing variables start and end are declared and used but the computed time_us value is only for commented-out logging. If timing is not actively needed, consider removing this instrumentation code.

Copilot uses AI. Check for mistakes.
Comment thread src/brpc/policy/baidu_rpc_protocol.cpp Outdated

char header_buf[12];
const size_t n = source->copy_to(header_buf, sizeof(header_buf));
size_t n = 0;

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.

I think it would be better to put the baidu_rpc_protocol.cpp file containing GPU functionality into a separate file called baidu_rpc_with_gpu_protocol.cpp, or to define a new protocol. Currently, the macro definitions generate too many if-else statements.

@randomkang randomkang Nov 14, 2025 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Maybe put GPU functionality into a separate file or define a new protocol is two heavy, we reorg the code to decrease the macro definitions. Is it ok? @yanglimingcn

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.

I think it's better to separate new protocol file for GDR feature, Besides, I have a idea we can also define a new protocol meta that includes both request meta and response meta carrying the GDR attribute value., the corresponding meta should also contain the GPU address information that both sides need to register with IB. Doing the GDR operations inside processRequest and ProcessRpcResponse method step will probably be more reasonable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If the server or client wants to know the GPU memory region of the other side, we can send this information with a separate rpc transport. But i do not see the the benefits of this approach. Can you explain more?

Comment thread src/brpc/rdma/rdma_endpoint.cpp Resolved
@yanglimingcn

Copy link
Copy Markdown
Contributor

I believe the long-term solution is to have users register memory with RDMA, allowing users to customize this memory based on their data organization methods.
https://zhuanlan.zhihu.com/p/376989325 This link contains some relevant details.

@randomkang

Copy link
Copy Markdown
Contributor Author

I believe the long-term solution is to have users register memory with RDMA, allowing users to customize this memory based on their data organization methods. https://zhuanlan.zhihu.com/p/376989325 This link contains some relevant details.

In this pr, brpc will recv data into fragmented gpu blocks and user must call IOBuf::copy_from_gpu to copy these gpu blocks into a continous hbm in order to use it. The d2d copy is time-consuming and not necessary.

In feature, we can let the user assign the gpu destination directly with interface like "rdma_memory_pool_user_specified_memory" and recv the data into it with the opcode like IBV_WR_RDMA_READ/IBV_WR_RDMA_WRITE. Then we can skip the d2d copy.

Futhermore, we can parse the brpc protocol with gpu kernel and initiate RDMA communication with GPU. Then the control path and the data path both are on gpu like nccl gin.

@randomkang

Copy link
Copy Markdown
Contributor Author

@yanglimingcn @zchuango all comments have been resolved, please review again.

Comment thread docs/cn/gdr.md Resolved
Comment thread docs/cn/gdr.md Resolved
Comment thread src/brpc/acceptor.h Resolved
Comment thread src/butil/gpu/gpu_block_pool.cpp Resolved
@yanglimingcn

Copy link
Copy Markdown
Contributor

Turn multiple commits into one

@randomkang

Copy link
Copy Markdown
Contributor Author

Turn multiple commits into one

ok

@yanglimingcn

Copy link
Copy Markdown
Contributor

LGTM

return (void *)d_data;
}

void* get_cpu_mem(int gpu_id, int64_t cpu_mem_size) {

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.

这个入参,应该是gpu_id吗

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

是的。这里申请的是pinned memory,使用pinned memory和显存进行交换,会比普通的memory快。在使用cudaMallocHost分配pinned memory的时候,建议先执行cudaSetDevice,这样分配的pined memory就会与对应的gpu id关联。

Comment thread src/butil/gpu/gpu_block_pool.cpp Outdated

std::atomic<BlockPoolAllocators*> BlockPoolAllocators::instance_{nullptr};

BlockPoolAllocators* BlockPoolAllocators::singleton() {

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.

你看brpc自带的singleton.h是否能满足你的需求。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

我看了下,可以用brpc自带的Singleton,已改

Comment thread src/butil/iobuf.cpp Outdated

void* IOBuf::get_first_data_ptr() {
if (_ref_num() == 0) {
return 0;

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.

这里返回nullptr

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK

@randomkang

Copy link
Copy Markdown
Contributor Author

@yanglimingcn @zchuango Are there any other issues with this pr?

@chenBright
chenBright removed the request for review from Copilot October 8, 2026 04:39

@wwbmmm wwbmmm 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.

[replied by brpc-oncall robot] The latest push fixes most earlier findings; three issues remain on the newly changed lines: the reverted socket-mode comment, MLX5_SCATTER_TO_CQE being set after the RDMA context is already open, and an early return that leaves the transport partially initialized.

Comment thread src/brpc/acceptor.h Outdated
std::shared_ptr<SocketSSLContext> _ssl_ctx;

// Choose to use a certain socket: 0 TCP, 1 RDMA
// Choose to use a certain socket: 0 TCP, 1 RDMA, 2 UBRING, 3 GDR

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.

[replied by brpc-oncall robot] The merge-conflict resolution reverted this comment to 2 UBRING, 3 GDR, but the enum is SOCKET_MODE_URMA = 3, SOCKET_MODE_GDR = 4 (see src/brpc/socket_mode.h). Please restore 0 TCP, 1 RDMA, 2 UBRING, 3 URMA, 4 GDR so it stays in sync with the enum.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok

Comment thread src/brpc/rdma/rdma_helper.cpp Outdated
// is enabled, small messages are inlined directly into host memory, which
// breaks this requirement and causes operations to fail. Force it to 0
// regardless of the user-provided value.
if (setenv("MLX5_SCATTER_TO_CQE", "0", 1) != 0) {

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.

[replied by brpc-oncall robot] MLX5_SCATTER_TO_CQE is set here, i.e. after the RDMA context has already been created: TransportFactory::ContextInitOrDie calls RdmaTransport::ContextInitOrDie() (which calls GlobalRdmaInitializeOrDie() → ibv_open_device) before GdrContextInitOrDie(), and the example also calls GlobalRdmaInitializeOrDie() first. libmlx5 reads the MLX5_* environment variables when the device context is created, so setting it after ibv_open_device has no effect. Please move it before the RDMA global initialization (or drop it and keep the value as an externally documented prerequisite).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok

Comment thread src/brpc/rdma_transport.cpp Outdated
_rdma_state = RDMA_OFF;
_socket = socket;
_default_connect = options.app_connect;
return;

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.

[replied by brpc-oncall robot] This early return happens before _on_edge_trigger is set and before _tcp_transport is created/initialized, while _rdma_state is set to RDMA_OFF. RdmaTransport::CutFromIOBuf/CutFromIOBufList/WaitEpollOut dereference _tcp_transport unconditionally, so any later call on this transport would crash. Even though the socket was just SetFailed, please leave the transport in a fully initialized state (e.g. fall through to the common tail of DoInit) rather than returning in the middle.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok

@randomkang

Copy link
Copy Markdown
Contributor Author

Is there other problem for this pr? @wwbmmm @chenBright

@chenBright
chenBright requested a balanced review from Copilot October 20, 2026 16:18

Copilot AI 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.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

23 open issues

And 3 more that still need to be addressed.

Review effort: Lite

Comment thread example/rdma_performance/client.cpp Resolved
Comment on lines +482 to +494
#if BRPC_WITH_GDR
// GPUDirect RDMA requires all received data to be written into the GPU
// memory buffer specified at post-receive time. When MLX5_SCATTER_TO_CQE
// is enabled, small messages are inlined directly into host memory, which
// breaks this requirement and causes operations to fail. libmlx5 reads
// MLX5_* environment variables when the device context is created (i.e.
// inside ibv_open_device below), so it must be forced to 0 before any
// device is opened, regardless of the user-provided value.
if (setenv("MLX5_SCATTER_TO_CQE", "0", 1) != 0) {
PLOG(ERROR) << "Fail to set MLX5_SCATTER_TO_CQE to 0";
ExitWithError();
}
#endif // if BRPC_WITH_GDR

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If user do not want to use gdr, then should not build brpc with gdr, and this environment variable setting will not take effect.

@randomkang

Copy link
Copy Markdown
Contributor Author

All Copilot issues have been resolved or commented. @chenBright @wwbmmm

@wwbmmm

wwbmmm commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

please resolve merge conflicts

@wwbmmm wwbmmm 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.

[replied by brpc-oncall robot] Most previous findings are now correctly addressed (prefetch size derived from the CPU pool block size, bool-returning GPU buffer fills with error propagation, cut/append failure checks, DEBUG-only timing, GetGPUIndex in the example). Two items remain: the GDR init ordering in the example client (below) and the still-unaddressed IOBuf::copy_from_gpu() device-memory validation; the Bazel GDR build also still relies on ambient C_INCLUDE_PATH/LD_LIBRARY_PATH rather than declaring the CUDA dependency.

}
#ifdef BRPC_WITH_GDR
if (FLAGS_use_gdr) {
brpc::rdma::GlobalGdrInitializeOrDie();

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.

[replied by brpc-oncall robot] With the else if turned into a plain if, --use_rdma=false --use_gdr=true now calls GlobalGdrInitializeOrDie() while RDMA is still uninitialized. GlobalGdrInitializeOrDieImpl passes GetRdmaPd() to InitGPUBlockPool, but g_pd is only set by GlobalRdmaInitializeOrDieImpl, so it is still nullptr: ibv_reg_mr(nullptr, ...) fails and BlockPoolAllocator hits its LOG(FATAL). Please make GDR imply the RDMA initialization here, e.g. initialize RDMA when either flag is set:

if (FLAGS_use_rdma || FLAGS_use_gdr) {
    brpc::rdma::GlobalRdmaInitializeOrDie();
}
#ifdef BRPC_WITH_GDR
if (FLAGS_use_gdr) {
    brpc::rdma::GlobalGdrInitializeOrDie();
}
#endif

Alternatively drop both explicit calls and rely on TransportFactory::ContextInitOrDie, as server.cpp already does.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

when gdr is open, rdma must be open.Flag validation has been added.

@randomkang

Copy link
Copy Markdown
Contributor Author

please resolve merge conflicts

Done

@randomkang

randomkang commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor Author

fix code conflicts

@wwbmmm wwbmmm 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.

[replied by brpc-oncall robot] The conflict resolution accidentally reverted the LOG(FATAL) → LOG(ERROR) change that chenBright asked for and you agreed to; please restore it. Apart from that the head matches the previously reviewed version.

void* prefetch_d2h_data = NULL;
bool is_gpu_memory = source->is_gpu_memory();
if (!is_gpu_memory) {
LOG(FATAL) << "RpcMessage is not in gpu!!!";

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.

[replied by brpc-oncall robot] This was changed to LOG(ERROR) in 3d0a06e in response to chenBright's comment, but the merge-conflict resolution restored the old version of this file and the LOG(FATAL) is back (the rest of the file, e.g. FillReqBufGpu, kept the LOG(ERROR) form). Please keep LOG(ERROR) here as well so an unexpected non-GPU buffer cannot abort the whole process.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The LOG(FATAL) → LOG(ERROR) change has been reverted.

1) recv all data on gpu first
2) the gpu block is alloced from a gpu block pool
3) brpc header, meta and body will be copied from gpu to cpu to process.
4) To decrease the d2h counts when processing header, meta and body, we will prefetch 512B to memory

Co-authored-by: sunce4t <847480001@qq.com>
#if BRPC_WITH_GDR
bool is_gpu_memory = msg->payload.is_gpu_memory();
if (is_gpu_memory) {
if (!FillReqBufGpu(&req_buf, msg.get(), body_without_attachment_size)) {

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.

Only the message body is copied back to host memory here; attachments remain in GPU memory. When a request has a non-empty attachment and both CRC32C and checksum_with_attachment are enabled, DeserializeRpcMessage() below passes the GPU memory address directly to CPU CRC32C through IOBufAsZeroCopyInputStream, causing an invalid memory access. The four-byte checksum check does not prevent this path, and the same issue applies on the response side.

@randomkang randomkang Nov 9, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK,the checksum_with_attachment has been supported when gdr is open. When gdr and checksum_with_attachment both are open, 1) the data will be copied from gpu to cpu and the checksum will be calced in sender 2) the data also wiil be copied from gpu to cpu and checksum will be checked in receiver.

Comment thread src/butil/config.h.in Outdated
#ifdef BRPC_WITH_GDR
#undef BRPC_WITH_GDR
#endif
#cmakedefine BRPC_WITH_GDR @WITH_GDR_VAL@

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.

#cmakedefine BRPC_WITH_GDR checks the CMake variable of the same name, but only WITH_GDR_VAL and -DBRPC_WITH_GDR=1 are currently set. As a result, when WITH_GDR=ON is enabled, this generated header first #undefs the macro and leaves it undefined, so the GPU implementation being compiled cannot see the GPU interfaces in subsequent headers. Please generate a numeric macro directly from WITH_GDR_VAL, or also set the variable used by #cmakedefine.

@randomkang randomkang Nov 9, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok,just as BRPC_WITH_RDMA, the definition of BRPC_WITH_GDR should not appear here and we have removed it.

saved_errno, "Fail to create RdmaEndpoint: %s", berror(saved_errno));
// No endpoint: later code paths must not assume RDMA is active
// and dereference _rdma_ep. Mark the transport as not using RDMA.
_rdma_state = RDMA_OFF;

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.

When endpoint allocation fails, Socket::OnCreated() has not yet called HoldHCRelatedRef(). For a new Socket with only its creation reference, SetFailed() here synchronously releases the last reference and returns the object to the resource pool, but DoInit() and OnCreated() then continue initializing it. Setting RDMA_OFF only prevents access to a null endpoint; it does not resolve the lifetime issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok, when rdma ep create is failed, report error log and fallback to tcp, instead of socket SetFailed. The code has been updated.

Comment thread src/brpc/channel.cpp
} else if (opt.socket_mode == SOCKET_MODE_URMA) {
buf.append("|urma");
} else if (opt.socket_mode == SOCKET_MODE_GDR) {
buf.append("|gdr");

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.

With the default configuration, ComputeChannelSignature() returns a zero signature at the start and never reaches |gdr here. If a GDR Channel is created for an address first, followed by a regular baidu_std Channel for the same address, SocketMap::Insert() reuses the former's Socket. The regular channel will therefore also receive attachments in GPU memory, and the existing response_attachment().copy_to() will read the device address directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK,We limit that only SOCKET_MODE_TCP can return ComputeChannelSignature().

return -1;
#if BRPC_WITH_GDR
if (_use_gdr) {
if (DoPostRecvGDR(_rbuf_data[_rq_received], g_gdr_recv_block_size, lkey) < 0) {

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.

When rdma_recv_zerocopy=false, a zero-byte ACK does not enter the GDR zerocopy=true branch above and instead leads to PostRecv(1, false). This reuses the GPU memory address, but lkey remains at its initial value of 0; the next data receive may therefore cause a protection error and disconnect the connection.

@randomkang randomkang Nov 9, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Because the rdma_recv_zerocopy is true at default, this issue has not been exposed. When wc.byte_len is 0, the zerocopy also should be true when gdr is open. Code has been updated.

Comment thread src/butil/iobuf.cpp Outdated
auto deleter = [](void* data) { free(data); };
out->append_user_data(mem, saved_n, deleter);
}
pop_front(saved_n);

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.

append_user_data() can return -1 if block metadata allocation fails, but this code still calls pop_front(saved_n) and returns a successful length. The destination receives no data, while the source data has already been consumed and the temporary host memory leaks. The protocol layer's length check also incorrectly reports success.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok, the result check of append_user_data has been added.

// owned by anyone; release it back to the pool instead of leaking.
LOG(ERROR) << "Fail to append prefetched meta to IOBuf";
host_allocator->DeallocateRaw(prefetch_d2h_data);
return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA);

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.

Before returning here, the code has already called pop_front(12) and obtained msg. Returning NOT_ENOUGH_DATA at this point makes the caller wait for new input, but the next parse still starts at the PRPC frame header while the source buffer now starts at the metadata. Meanwhile, msg is not returned to the message pool.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK,the error has been changed from PARSE_ERROR_NOT_ENOUGH_DATA to PARSE_ERROR_ABSOLUTELY_WRONG.

meta.attachment_size(), res_size);
return false;
}
int body_without_attachment_size = res_size - meta.attachment_size();

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.

This omits the attachment_size < 0 check present in the CPU response path. If the current payload contains only A and the attachment length is -1, the message body length is calculated as 2. When the prefetch also includes the next frame, the fast path copies out AP, where P comes from the next frame's PRPC. The SerializedResponse path passes it directly to the caller as a successful response.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK,we align the check with the CPU response path。

ComputeChannelSignature() returned a zeroized signature early for the
default configuration, without reaching the |rdma/|urma/|gdr suffix,
so a GDR/RDMA/URMA channel and a plain TCP channel for the same server
address mapped to the same SocketMap key. A plain channel could then
reuse a GDR socket and receive attachments in GPU memory, causing
response_attachment().copy_to() to read a device address directly.

Now the zero-signature shortcut is only taken when socket_mode is
SOCKET_MODE_TCP, so non-TCP channels always hash to a distinct
signature and never share sockets with plain channels.
With rdma_recv_zerocopy=false, a zero-byte receive completion (e.g. an
empty ack) skipped the _use_gdr branch that forces zerocopy, so
PostRecv(1, false) reposted the previous GPU buffer address while the
local lkey stayed at its initial 0. The next receive on that WR hit a
local protection error and disconnected the connection.

Now the zero-byte branch also forces zerocopy in GDR mode so the GPU
receive buffer is always reposted through the path that allocates a
fresh device block and fetches its lkey. Non-GDR endpoints keep their
original behavior.
append_user_data() inside cutn_from_gpu() may fail when the block
metadata allocation fails, but the return value was ignored: the
temporary host buffer leaked, the source was still consumed by
pop_front() while the destination received nothing, and the returned
length falsely reported success to the protocol layer's length check.

Now check the return of append_user_data(): on failure release the
temporary buffer, leave the source intact and return 0 like the other
failure paths so the caller can retry.
When append_user_data_with_meta() failed after the 12-byte PRPC header
had already been popped from the source, the parser returned
PARSE_ERROR_NOT_ENOUGH_DATA: InputMessenger then kept the source
waiting for more input and re-parsed starting at the meta bytes,
permanently desynchronizing the stream, while the MostCommonMessage
leaked and was never returned to its object pool.

The header is already consumed so this error is unrecoverable:
return PARSE_ERROR_ABSOLUTELY_WRONG to close the connection and
Destroy() the message to return it to the pool.
The CPU response path rejects attachment_size < 0, but the GDR path
only checked attachment_size > res_size: a negative attachment_size
made body_without_attachment_size = res_size + 1, so the prefetch fast
path copied one byte beyond the frame (e.g. the 'P' of the next
frame's PRPC header when the prefetch covered it) into the body and
handed the corrupted response to the caller as a success.

Mirror the CPU path: reject attachment_size < 0 as well, with the same
error message.
BRPC_WITH_GDR was previously emitted by config.h.in via
"#define BRPC_WITH_GDR @WITH_GDR_VAL@". Independent subprojects that
regenerate their own config.h (e.g. the flatbuffers codegen tests) do
not inherit the top-level CMake variables, so the substitution produced
a value-less "#define BRPC_WITH_GDR" and broke every
"#if BRPC_WITH_GDR" in the included headers.

BRPC_WITH_RDMA/URMA/UBRING are never defined in config.h: they are
always passed as -D flags by all three build systems (CMake
BRPC_COMMON_DEFINITIONS, Bazel defines select, config_brpc.sh CPPFLAGS).
Make BRPC_WITH_GDR follow the same convention:

- config.h.in: drop the BRPC_WITH_GDR block; the macro comes from the
  command line only and stays undefined (evaluates to 0) when absent.
- test/flatbuffers_codegen/CMakeLists.txt: no WITH_GDR_VAL substitution
  is needed anymore.
Align two files that differ from master only by added comments:
- config.h.in: the BRPC_WITH_GDR block was already fully removed; also
  drop the leftover explanation comment (master has no GDR content at
  all, and the -D flag convention matches BRPC_WITH_RDMA).
- test/flatbuffers_codegen/CMakeLists.txt: drop the comment explaining
  why WITH_GDR_VAL is not substituted; the file is now identical to
  master.

The socket_mode enumeration comment in acceptor.h is kept: it
documents the values set by the GDR acceptor path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

brpc支持GPU Direct RDMA

6 participants