-
Notifications
You must be signed in to change notification settings - Fork 24.1k
RPC: use RDMA completion channel to not spin #29440
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,8 @@ | |
| #ifdef GGML_RPC_RDMA | ||
| # include <infiniband/verbs.h> | ||
| # include <array> | ||
| # include <cerrno> | ||
| # include <chrono> | ||
| # include <time.h> | ||
| # ifndef _WIN32 | ||
| # include <poll.h> | ||
|
|
@@ -54,13 +56,18 @@ using rdma_gid_t = std::array<uint8_t, RDMA_GID_SIZE>; | |
| #if defined(GGML_RPC_RDMA) && !defined(GGML_RPC_RDMA_APPLE) | ||
| static constexpr size_t RDMA_CHUNK = 256 * 1024; // 256 KiB per send/recv (fits default 8 MiB memlock) | ||
| static constexpr int RDMA_RX_DEPTH = 24; // pre-posted recv ring: 24 × 256 KiB = 6 MiB | ||
| // keep polling the CQ for this long after the last activity, then sleep until the next completion | ||
| static constexpr auto RDMA_SPIN_TIME = std::chrono::milliseconds(100); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't quite understand why we need this but I guess this is how it works
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My guess is that if we sleep unconditionally without a spin/polling phase, there will be a performance hit from the large amount of sleep + wake cycles. |
||
|
|
||
| struct rdma_conn { | ||
| struct ibv_context * ctx = nullptr; | ||
| struct ibv_pd * pd = nullptr; | ||
| struct ibv_cq * scq = nullptr; // send completions | ||
| struct ibv_cq * rcq = nullptr; // recv completions | ||
| struct ibv_qp * qp = nullptr; | ||
| struct ibv_comp_channel * ch = nullptr; // CQ events, so an idle connection can sleep instead of spinning | ||
|
|
||
| std::chrono::steady_clock::time_point last_active; // last completion or posted send | ||
|
|
||
| void * tx_buf = nullptr; | ||
| struct ibv_mr * tx_mr = nullptr; | ||
|
|
@@ -95,6 +102,7 @@ struct rdma_conn { | |
| if (qp) ibv_destroy_qp(qp); | ||
| if (scq) ibv_destroy_cq(scq); | ||
| if (rcq) ibv_destroy_cq(rcq); | ||
| if (ch) ibv_destroy_comp_channel(ch); | ||
| if (pd) ibv_dealloc_pd(pd); | ||
| if (ctx) ibv_close_device(ctx); | ||
| } | ||
|
|
@@ -142,6 +150,7 @@ struct socket_t::impl { | |
| bool tcp_peer_closed(); | ||
| bool rdma_activate(uint32_t remote_qpn, uint32_t remote_psn, const uint8_t * remote_gid); | ||
| bool rdma_poll(struct ibv_cq * cq, struct ibv_wc * wc); | ||
| bool rdma_wait_event(); | ||
|
|
||
| std::unique_ptr<rdma_conn> rdma; | ||
| rdma_local_info rdma_local = {}; | ||
|
|
@@ -291,8 +300,10 @@ bool socket_t::impl::rdma_probe() { | |
| rdma->pd = ibv_alloc_pd(ibctx); | ||
| if (!rdma->pd) return false; | ||
|
|
||
| rdma->scq = ibv_create_cq(ibctx, 16, nullptr, nullptr, 0); | ||
| rdma->rcq = ibv_create_cq(ibctx, RDMA_RX_DEPTH + 4, nullptr, nullptr, 0); | ||
| // without a completion channel rdma_poll() spins all the time, as before | ||
| rdma->ch = ibv_create_comp_channel(ibctx); | ||
| rdma->scq = ibv_create_cq(ibctx, 16, nullptr, rdma->ch, 0); | ||
| rdma->rcq = ibv_create_cq(ibctx, RDMA_RX_DEPTH + 4, nullptr, rdma->ch, 0); | ||
| if (!rdma->scq || !rdma->rcq) return false; | ||
|
|
||
| ibv_qp_init_attr qia = {}; | ||
|
|
@@ -394,23 +405,70 @@ bool socket_t::impl::rdma_activate(uint32_t remote_qpn, uint32_t remote_psn, con | |
| } | ||
| } | ||
|
|
||
| rdma->last_active = std::chrono::steady_clock::now(); | ||
|
|
||
| GGML_LOG_INFO("RDMA activated: qpn=%u->%u mtu=%d rx_depth=%d\n", | ||
| rdma_local.qpn, remote_qpn, 128 << rdma_local.path_mtu, RDMA_RX_DEPTH); | ||
| return true; | ||
| } | ||
|
|
||
| // Sleep until the completion channel has an event or the TCP peer closes. | ||
| bool socket_t::impl::rdma_wait_event() { | ||
| rdma_conn * c = rdma.get(); | ||
| // POLLHUP and POLLERR are always reported, the TCP socket carries no data after the RDMA upgrade | ||
| struct pollfd pfds[2] = { | ||
| { c->ch->fd, POLLIN, 0 }, | ||
| { fd, POLLRDHUP, 0 }, | ||
| }; | ||
| if (poll(pfds, 2, -1) < 0) { | ||
| return errno == EINTR; | ||
| } | ||
| if (pfds[1].revents & (POLLHUP | POLLERR | POLLRDHUP)) { | ||
| return false; | ||
| } | ||
| if (pfds[0].revents & POLLIN) { | ||
| struct ibv_cq * ev_cq = nullptr; | ||
| void * ev_ctx = nullptr; | ||
| if (ibv_get_cq_event(c->ch, &ev_cq, &ev_ctx) != 0) { | ||
| return false; | ||
| } | ||
| ibv_ack_cq_events(ev_cq, 1); | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| bool socket_t::impl::rdma_poll(struct ibv_cq * cq, struct ibv_wc * wc) { | ||
| for (uint64_t s = 0; ; s++) { | ||
| rdma_conn * c = rdma.get(); | ||
| bool armed = false; | ||
| for (uint64_t s = 1; ; s++) { | ||
| int n = ibv_poll_cq(cq, 1, wc); | ||
| if (n > 0) { | ||
| c->last_active = std::chrono::steady_clock::now(); | ||
| if (wc->status != IBV_WC_SUCCESS) { | ||
| GGML_LOG_ERROR("RDMA CQ wc error: status=%d (%s) vendor_err=0x%x\n", | ||
| wc->status, ibv_wc_status_str(wc->status), wc->vendor_err); | ||
| } | ||
| return wc->status == IBV_WC_SUCCESS; | ||
| } | ||
| if (n < 0) return false; | ||
| if ((s & 0xFFFFF) == 0 && s > 0) { | ||
| if (armed) { | ||
| // armed and still empty: sleep until the next completion | ||
| if (!rdma_wait_event()) { | ||
| return false; | ||
| } | ||
| armed = false; | ||
| continue; | ||
| } | ||
| // spin while the connection is busy, arm the CQ once it has been idle for RDMA_SPIN_TIME | ||
| // a completion that arrives before arming raises no event, so poll once more after arming | ||
| if (c->ch && (s & 0x3FF) == 0 && std::chrono::steady_clock::now() - c->last_active > RDMA_SPIN_TIME) { | ||
| if (ibv_req_notify_cq(cq, 0) != 0) { | ||
| return false; | ||
| } | ||
| armed = true; | ||
| continue; | ||
| } | ||
| if ((s & 0xFFFFF) == 0) { | ||
| if (tcp_peer_closed()) { | ||
| return false; | ||
| } | ||
|
|
@@ -444,6 +502,7 @@ bool socket_t::impl::rdma_send(const void * data, size_t size) { | |
| } | ||
|
|
||
| if (ibv_post_send(c->qp, &wr, &bad) != 0) return false; | ||
| c->last_active = std::chrono::steady_clock::now(); | ||
| struct ibv_wc wc; | ||
| if (!rdma_poll(c->scq, &wc)) return false; | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.