Repository navigation
Description
How is this issue impacting you?
Data corruption
Share Your Debug Logs
v3.8.0-0 release build, GPU NIC handler, 2 PEs on 2 nodes. PE 1 is SIGKILLed after iteration 4. PE 0
(NVSHMEM_DEBUG=INFO):
./src/modules/transport/ibgda/ibgda.cpp 5287 NIC handler will be GPU.
PE 0 iter 5: put + signal + nvshmem_quiet() returned after 3672.6 ms
PE 0 iter 6: put + signal + nvshmem_quiet() returned after 1.1 ms
PE 0: all 40 iterations returned; exiting without nvshmem_finalize
Iteration 5 is the first one after the kill. It returns when the IB retries run out (3.5–3.7 s with timeout 14 and
retry count 7); iterations 6–39 return in about 1 ms. No WARN, no device printf, exit code 0. Reading the RC send CQ
afterwards shows the error CQE that was dropped:
CQSCAN cq=1 type=rc qpn=0x15966 ncqes=1024 valid=1 err=1 syndrome=0x05 vendor=0xf9 last_opcode=0xd
(opcode 0xd REQ_ERR, syndrome 0x05 WR_FLUSH_ERR). Without the kill no run had an error CQE. Log excerpts are attached below (host names and addresses redacted; the full logs are available on request).
Steps to Reproduce the Issue
- Build v3.8.0-0 as a release build (the default) with IBGDA enabled.
- Run 2 PEs, one per node:
NVSHMEM_IB_ENABLE_IBGDA=1 NVSHMEM_IBGDA_NIC_HANDLER=gpu NVSHMEM_IB_TIMEOUT=14 NVSHMEM_IB_RETRY_CNT=7 NVSHMEM_IBGDA_NUM_RC_PER_PE=1. - PE 0 runs 40 iterations of a single-thread kernel and polls the stream with
cudaStreamQuery; PE 1 waits on the
signal withnvshmem_signal_wait_until:nvshmem_putmem_signal_nbi(dst, src, 256 << 10, sig, 1, NVSHMEM_SIGNAL_ADD, 1); nvshmem_quiet();
- SIGKILL PE 1 after iteration 4.
- Fetch variant: the same loop with
v = nvshmem_long_atomic_fetch_add(ctr, 1, 1)before the quiet, 160 iterations
15 ms apart; iteration k should return k.
The attached nvshmem_kill_repro.cu (public API only) does steps 1–4.
Intermittency. Every run: quiet 5/5 and 5/5 (3/3 in an earlier set), fetch 5/5. In the fetch runs the first 65–67
fetches were correct; the fetch whose quiet waited for the retries and all 93–95 after it returned a wrong value, and
nothing reported an error.
Previous success. Not a regression: v3.4.5-0 behaves the same (5/5, after about 57 s because of its IB timeout 20).
With NVSHMEM_IBGDA_NIC_HANDLER=cpu_host_memory the same happens once the doorbell-record fix from #117 is applied, so
that fix turns the #117 hang into this silent success.
NVSHMEM Version
3.8.0+cuda12.8 (v3.8.0-0, 270759e5, built from source). The same code is in devel cffd2a51, v3.5.19-1 and
v3.4.5-0.
Your platform details
- GPU & Network: 2 nodes, one GPU and one NIC each: Quadro RTX 5000 (sm_75) and RTX A4000 (sm_86); ConnectX-6
(fw 20.43.4100), RoCE v2, back-to-back 100 GbE. - Environment: bare metal, CUDA 12.8, drivers 570.211.01 and 580.178.04, MLNX_OFED 23.10 and 25.10,
PeerMappingOverride=1. - Scalability: 2 PEs, one per node.
Error Message & Behavior
First error. None, and that is the problem.
Expected. An error CQE is fatal, as the code comment says, or at least visible to the application.
Actual. Release builds read the error CQE and drop it. nvshmem_quiet() returns as if the puts had completed, and
fetching operations return a value although their completion failed.
Where (src/include/non_abi/device/pt-to-pt/ibgda_device.cuh, devel cffd2a51; one line earlier in v3.8.0-0):
ibgda_poll_cq()detectsMLX5_CQE_REQ_ERR, sets*errorand returns -1 (606–628). Its printf is
#ifdef NVSHMEM_IBGDA_DEBUG.ibgda_quiet()(1721–1735) only doesassert(likely(status == 0)), under
// TODO: Integrate the error handler with the core NVSHMEM.ibgda_wait_for_slot_availability()(1737–1754)
prints and continues.- Release builds compile with
-O3 -DNDEBUGand defineNVSHMEM_IBGDA_DEBUGonly for Debug (src/CMakeLists.txt
531), so both the assert and the printf are gone. These entry points areNVSHMEMI_NOINLINEinside
libnvshmem_device.a, so the application's own flags do not matter;cuobjdumpshows no__assertfailin
transfer_device.cu.oof the release library (nor in the 3.4.5 pip wheel). - Fetches:
nvshmemi_ibgda_rma_g_impl()(2892–2899) andnvshmemi_ibgda_amo_fetch_impl()(3241–3248) call
ibgda_quiet()and then return*laddr, which still holds what an earlier operation left in that slot.
With the GPU handler no host progress function is registered (ibgda.cpp 5308 in v3.8.0-0), so the host does not see
the error either.
Suggested fix. In every build type, fail on status != 0 in ibgda_quiet() and
ibgda_wait_for_slot_availability() (printf plus __trap(), so the host gets a CUDA error), and do not return the
stale slot from a fetch whose completion failed. Recording the error where the application can query it would fit the
device-side diagnostics item of #112. This differs from #117: there no CQE was produced and quiet hung; here the CQE
is produced and then discarded.
nvshmem_kill_repro.cu (public API only, steps 1–4)
// kill_repro.cu - what nvshmem_quiet() does on IBGDA after the peer PE is killed.
// Uses only the public NVSHMEM API, so it runs against an unmodified NVSHMEM build.
//
// PE 0 loops: one kernel per iteration doing nvshmem_putmem_signal_nbi() to PE 1 and then
// nvshmem_quiet(). PE 1 waits for each signal and prints it. The runner SIGKILLs PE 1 mid-loop.
// PE 0's host bounds every iteration: if the kernel has not finished after hang_s seconds, it
// reports that nvshmem_quiet() did not return and exits without nvshmem_finalize() (which
// cannot complete with a dead peer anyway).
//
// FINALIZE=1 (env, optional): wherever the run ends (all iterations returned, the hang_s bound,
// or a failed kernel), the PE calls nvshmem_finalize() under a 30 s watchdog thread and prints
// "PE <n>: nvshmem_finalize returned after X ms" or
// "PE <n>: nvshmem_finalize did not return after 30 s". The exit code is the one of the end path
// (0, 3 or 4) if finalize returned, and 5 if it did not. Without FINALIZE the program never calls
// nvshmem_finalize, as before.
//
// usage: nvs_kill_repro <rank 0|1> <pe0_ip> <tcp_port> [iters] [bytes] [period_ms] [hang_s]
// Bootstrap: NVSHMEM unique id, sent from PE 0 to PE 1 over a plain TCP socket.
#include <atomic>
#include <thread>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
#include <cuda_runtime.h>
#include <nvshmem.h>
#include <nvshmemx.h>
#define CK(x) \
do { \
cudaError_t e_ = (x); \
if (e_ != cudaSuccess) { \
fprintf(stderr, "%s:%d %s: %s\n", __FILE__, __LINE__, #x, cudaGetErrorString(e_)); \
_exit(4); \
} \
} while (0)
static double now_ms() {
timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return t.tv_sec * 1e3 + t.tv_nsec / 1e6;
}
// ---- FINALIZE=1: nvshmem_finalize() at the end, bounded by a watchdog thread ----
static const int FINALIZE_BOUND_S = 30;
static bool g_finalize = false;
static std::atomic<int> g_fin_state{0}; // 0 running, 1 returned, 2 watchdog fired
// Ends the process. With FINALIZE=1 it first calls nvshmem_finalize(); a watchdog thread exits
// with code 5 if that has not returned after FINALIZE_BOUND_S.
[[noreturn]] static void end_run(int mype, int code) {
fflush(stdout);
if (g_finalize) {
double t0 = now_ms();
std::thread([mype, t0]() {
while (now_ms() - t0 < FINALIZE_BOUND_S * 1e3) {
if (g_fin_state.load() != 0) return;
usleep(10000);
}
int running = 0;
if (!g_fin_state.compare_exchange_strong(running, 2)) return;
char m[96];
int n = snprintf(m, sizeof m, "PE %d: nvshmem_finalize did not return after %d s\n", mype,
FINALIZE_BOUND_S);
if (n > 0 && write(1, m, (size_t)n) < 0) {}
_exit(5);
}).detach();
nvshmem_finalize();
int running = 0;
if (!g_fin_state.compare_exchange_strong(running, 1))
for (;;) pause(); // the watchdog fired first and is exiting
printf("PE %d: nvshmem_finalize returned after %.1f ms\n", mype, now_ms() - t0);
fflush(stdout);
}
_exit(code);
}
static int xfer(int fd, void *p, size_t n, int send_) {
char *c = (char *)p;
while (n) {
ssize_t r = send_ ? send(fd, c, n, 0) : recv(fd, c, n, 0);
if (r <= 0) return -1;
c += r;
n -= r;
}
return 0;
}
__global__ void put_signal_quiet(void *dst, const void *src, size_t bytes, uint64_t *sig, int peer) {
if (threadIdx.x == 0 && blockIdx.x == 0) {
nvshmem_putmem_signal_nbi(dst, src, bytes, sig, 1, NVSHMEM_SIGNAL_ADD, peer);
nvshmem_quiet();
}
}
__global__ void wait_signal(uint64_t *sig, uint64_t value) {
if (threadIdx.x == 0 && blockIdx.x == 0) nvshmem_signal_wait_until(sig, NVSHMEM_CMP_GE, value);
}
int main(int argc, char **argv) {
if (argc < 4) {
fprintf(stderr, "usage: %s <rank 0|1> <pe0_ip> <tcp_port> [iters] [bytes] [period_ms] [hang_s]\n",
argv[0]);
return 1;
}
int rank = atoi(argv[1]);
const char *pe0_ip = argv[2];
int port = atoi(argv[3]);
int iters = argc > 4 ? atoi(argv[4]) : 40;
size_t bytes = argc > 5 ? strtoul(argv[5], 0, 10) : 262144;
int period_ms = argc > 6 ? atoi(argv[6]) : 250;
int hang_s = argc > 7 ? atoi(argv[7]) : 30;
const char *fin = getenv("FINALIZE");
g_finalize = fin && fin[0] && strcmp(fin, "0") != 0;
setvbuf(stdout, NULL, _IOLBF, 0);
nvshmemx_uniqueid_t id = NVSHMEMX_UNIQUEID_INITIALIZER;
int one = 1;
sockaddr_in a{};
a.sin_family = AF_INET;
a.sin_port = htons(port);
if (rank == 0) {
nvshmemx_get_uniqueid(&id);
int ls = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
a.sin_addr.s_addr = INADDR_ANY;
if (bind(ls, (sockaddr *)&a, sizeof a) || listen(ls, 1)) {
perror("bind/listen");
return 1;
}
int cs = accept(ls, 0, 0);
if (cs < 0 || xfer(cs, &id, sizeof id, 1)) {
perror("send id");
return 1;
}
close(cs);
close(ls);
} else {
inet_pton(AF_INET, pe0_ip, &a.sin_addr);
int cs = -1;
for (int tries = 0;; tries++) {
cs = socket(AF_INET, SOCK_STREAM, 0);
if (connect(cs, (sockaddr *)&a, sizeof a) == 0) break;
close(cs);
if (tries > 300) {
fprintf(stderr, "cannot reach PE 0\n");
return 1;
}
usleep(200000);
}
if (xfer(cs, &id, sizeof id, 0)) {
perror("recv id");
return 1;
}
close(cs);
}
CK(cudaSetDevice(0));
nvshmemx_init_attr_t attr = NVSHMEMX_INIT_ATTR_INITIALIZER;
nvshmemx_set_attr_uniqueid_args(rank, 2, &id, &attr);
if (nvshmemx_init_attr(NVSHMEMX_INIT_WITH_UNIQUEID, &attr)) {
fprintf(stderr, "nvshmemx_init_attr failed\n");
return 2;
}
int mype = nvshmem_my_pe();
char *dst = (char *)nvshmem_malloc(bytes);
char *src = (char *)nvshmem_malloc(bytes);
uint64_t *sig = (uint64_t *)nvshmem_malloc(sizeof(uint64_t));
if (!dst || !src || !sig) {
fprintf(stderr, "nvshmem_malloc failed\n");
return 2;
}
CK(cudaMemset(sig, 0, sizeof(uint64_t)));
CK(cudaMemset(src, 0x5a, bytes));
CK(cudaDeviceSynchronize());
nvshmem_barrier_all();
printf("PE %d ready: iters=%d bytes=%zu period_ms=%d hang_s=%d%s\n", mype, iters, bytes, period_ms,
hang_s, g_finalize ? " finalize=1" : "");
const char *end_how = g_finalize ? "calling nvshmem_finalize" : "exiting without nvshmem_finalize";
cudaStream_t st;
CK(cudaStreamCreateWithFlags(&st, cudaStreamNonBlocking));
for (int i = 0; i < iters; i++) {
double t0 = now_ms();
if (mype == 0)
put_signal_quiet<<<1, 1, 0, st>>>(dst, src, bytes, sig, 1);
else
wait_signal<<<1, 1, 0, st>>>(sig, (uint64_t)i + 1);
CK(cudaGetLastError());
cudaError_t q;
while ((q = cudaStreamQuery(st)) == cudaErrorNotReady) {
if (now_ms() - t0 > hang_s * 1e3) {
if (mype == 0)
printf("PE 0 iter %d: nvshmem_quiet() has not returned after %d s; %s\n", i, hang_s,
end_how);
else
printf("PE 1 iter %d: no signal after %d s; %s\n", i, hang_s,
g_finalize ? end_how : "exiting");
end_run(mype, 3);
}
usleep(1000);
}
if (q != cudaSuccess) {
printf("PE %d iter %d: kernel failed: %s\n", mype, i, cudaGetErrorString(q));
end_run(mype, 4);
}
if (mype == 0)
printf("PE 0 iter %d: put + signal + nvshmem_quiet() returned after %.1f ms\n", i, now_ms() - t0);
else
printf("PE 1 iter %d: signal arrived after %.1f ms\n", i, now_ms() - t0);
if (mype == 0) usleep(period_ms * 1000);
}
// No nvshmem_finalize() by default: its barrier cannot complete once the peer is gone.
printf("PE %d: all %d iterations returned; %s\n", mype, iters, end_how);
end_run(mype, 0);
}PE 0 log excerpt, run 1 (NVSHMEM_DEBUG=INFO; transport lines, iterations and the CQ read)
./src/modules/transport/ibgda/ibgda.cpp 5126 NVSHMEM_IBGDA_NIC_HANDLER: gpu
...
./src/modules/transport/ibgda/ibgda.cpp 5272 NIC buffer will be on GPU memory.
./src/modules/transport/ibgda/ibgda.cpp 5287 NIC handler will be GPU.
...
./src/modules/transport/ibgda/ibgda.cpp 3439 Creating 1 RC QPs
...
./src/modules/transport/ibgda/ibgda.cpp 745 [0] IBGDA: device used mlx5_1, data_direct support: 0
...
PE 0 ready: iters=40 bytes=262144 period_ms=250 hang_s=30
CQSCAN prep queues=3 dcis=1
CQSCAN prep cq=0 type=dci qpn=0x15965 ncqes=1024 cqe=0x7f787fa90000 ptr_type=2 host_ptr=no
CQSCAN prep cq=2 type=rc qpn=0x15966 ncqes=1024 cqe=0x7f787fac0000 ptr_type=2 host_ptr=no
PE 0 iter 0: put + signal + nvshmem_quiet() returned after 2.1 ms
PE 0 iter 1: put + signal + nvshmem_quiet() returned after 1.1 ms
PE 0 iter 2: put + signal + nvshmem_quiet() returned after 1.1 ms
PE 0 iter 3: put + signal + nvshmem_quiet() returned after 1.1 ms
PE 0 iter 4: put + signal + nvshmem_quiet() returned after 1.1 ms
PE 0 iter 5: put + signal + nvshmem_quiet() returned after 3672.6 ms
PE 0 iter 6: put + signal + nvshmem_quiet() returned after 1.1 ms
PE 0 iter 7: put + signal + nvshmem_quiet() returned after 1.1 ms
...
PE 0 iter 39: put + signal + nvshmem_quiet() returned after 1.1 ms
PE 0: all 40 iterations returned; exiting without nvshmem_finalize
CQSCAN cq=0 type=dci qpn=0x15965 ncqes=1024 valid=1 err=0 last_opcode=0x0
CQSCAN cq=1 type=rc qpn=0x15966 ncqes=1024 valid=1 err=1 first_err_at=0 syndrome=0x05 vendor=0xf9 last_opcode=0xd
CQSCAN queues=2 total_err=1 path=kernel
PE 1 log excerpt, run 1 (the last signal it saw before the kill)
./src/modules/transport/ibgda/ibgda.cpp 5287 NIC handler will be GPU.
...
./src/modules/transport/ibgda/ibgda.cpp 745 [1] IBGDA: device used mlx5_0, data_direct support: 0
...
PE 1 ready: iters=40 bytes=262144 period_ms=250 hang_s=30
PE 1 iter 0: signal arrived after 2.2 ms
PE 1 iter 1: signal arrived after 250.5 ms
PE 1 iter 2: signal arrived after 251.6 ms
PE 1 iter 3: signal arrived after 250.5 ms
PE 1 iter 4: signal arrived after 251.5 ms
Activity
benhg commentedon Oct 15, 2026
Thank you for the report. It looks like a real issue. I'll take a look and get back to you.