Repository navigation
[BUG] Vamana build fails with cudaErrorInvalidValue on an empty reverse-edge batch #2767
Description
Describe the bug
cuvsVamanaBuild can fail with cudaErrorInvalidValue on valid float32 input. The captured failure is a reverse-edge raft::linalg::map_offset call with zero elements, which submits a CUDA launch with grid.x == 0.
The failure depends on native RNG state. Source inspection and controlled tests point to the first inserted point also being the graph's starting point. Removing that self-neighbor leaves the first insertion batch with no reverse edges.
This reproduces through the C API without Java, OpenSearch, concurrent builds, or changes to cuVS. It also reproduces in an October 6 nightly package.
Steps/Code to reproduce bug
- Activate a conda environment with cuVS installed.
- Save the standalone program below as
rng_control.c. - Compile and run the failure and control cases on an available GPU. The program creates synthetic input itself.
Standalone C reproducer
#include <cuvs/neighbors/vamana.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
/* Minimal deterministic C-API reproduction. No Java, threads, plugin, or custom kernels.
* Args: n dims degree visited native_rng_seed repetitions output_prefix
* n=410, native_rng_seed=1 reproduces the small-graph failure.
* Data is fixed; only the native RNG seed is varied for the paired control. */
int main(int argc,char **argv) {
if(argc!=8) return 2;
int n=atoi(argv[1]),dims=atoi(argv[2]),degree=atoi(argv[3]),visited=atoi(argv[4]);
unsigned seed=strtoul(argv[5],NULL,10);int repetitions=atoi(argv[6]);
float *data=malloc((size_t)n*dims*sizeof(float));
for(int i=0;i<n;i++)for(int d=0;d<dims;d++)data[(size_t)i*dims+d]=(float)(((i*31+d*7)%997)-498)/20.0f;
int64_t shape[2]={n,dims};DLManagedTensor tensor={0};
tensor.dl_tensor.data=data;tensor.dl_tensor.device=(DLDevice){kDLCPU,0};
tensor.dl_tensor.ndim=2;tensor.dl_tensor.dtype=(DLDataType){kDLFloat,32,1};tensor.dl_tensor.shape=shape;
char csvpath[4096],graph[4096];snprintf(csvpath,sizeof(csvpath),"%s.csv",argv[7]);
snprintf(graph,sizeof(graph),"%s.graph",argv[7]);
FILE *out=fopen(csvpath,"wx");if(!out){perror(csvpath);return 2;}
fprintf(out,"seed,n,dims,degree,visited,native_seed,distribution,resources,thread,gpu,start,duration_s,outcome,stage,error\n");
for(int i=0;i<repetitions;i++) {
struct timespec start,end;clock_gettime(CLOCK_REALTIME,&start);
cuvsResources_t resources; cuvsVamanaIndexParams_t params; cuvsVamanaIndex_t index;
if(cuvsResourcesCreate(&resources)!=CUVS_SUCCESS || cuvsVamanaIndexParamsCreate(¶ms)!=CUVS_SUCCESS || cuvsVamanaIndexCreate(&index)!=CUVS_SUCCESS) return 3;
params->graph_degree=degree;params->visited_size=visited;params->metric=L2Expanded;
params->vamana_iters=1;params->alpha=1.2f;params->max_fraction=0.06f;
params->batch_base=2;params->queue_size=127;params->reverse_batchsize=1000000;
srand(seed);
const char *stage="build";cuvsError_t rc=cuvsVamanaBuild(resources,params,&tensor,index);
if(rc==CUVS_SUCCESS){stage="serialize";rc=cuvsVamanaSerialize(resources,graph,index,false);}
clock_gettime(CLOCK_REALTIME,&end);
fprintf(out,"%d,%d,%d,%d,%d,%u,fixed,fresh,0,%s,%lld.%09ld,%.9f,%s,%s,\"",i,n,dims,degree,visited,seed,getenv("CUDA_VISIBLE_DEVICES"),(long long)start.tv_sec,start.tv_nsec,(end.tv_sec-start.tv_sec)+(end.tv_nsec-start.tv_nsec)*1e-9,rc==CUVS_SUCCESS?"ok":"failure",stage);
if(rc!=CUVS_SUCCESS){const char *s=cuvsGetLastErrorText();for(;s&&*s;s++){if(*s=='"')fputc('"',out);fputc(*s,out);}}
fprintf(out,"\"\n");fflush(out);
cuvsVamanaIndexDestroy(index);cuvsVamanaIndexParamsDestroy(params);cuvsResourcesDestroy(resources);unlink(graph);
}
fclose(out);free(data);return 0;
}gcc -std=c11 -D_POSIX_C_SOURCE=200809L -O2 \
-I"$CONDA_PREFIX/include" -I"$CONDA_PREFIX/targets/x86_64-linux/include" \
rng_control.c -L"$CONDA_PREFIX/lib" \
-Wl,--enable-new-dtags,-rpath,"$CONDA_PREFIX/lib" \
-lcuvs_c -o rng_control
export LD_LIBRARY_PATH="$CONDA_PREFIX/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
repro_dir="$(mktemp -d ./vamana-repro.XXXXXX)"
# Arguments: rows dimensions graph_degree visited_size native_seed repetitions output_prefix
CUDA_VISIBLE_DEVICES=0 ./rng_control 410 16 32 64 1 20 "$repro_dir/failure"
CUDA_VISIBLE_DEVICES=0 ./rng_control 410 16 32 64 2 20 "$repro_dir/control"Inspect failure.csv and control.csv in that directory. Build outcomes and error messages are recorded in the CSV; the harness does not use its exit status to count failed builds.
Both commands use identical fixed input. The only changed build condition is srand(1) versus srand(2), called immediately before each build. With glibc 2.39, seed 1 makes point 333 both the first inserted point and the starting point at N=410.
| Case | Observed build failures |
|---|---|
| N=410, D=16, degree=32, visited=64, native seed=1 | 20/20 |
| Same input and parameters, native seed=2 | 0/20 |
The issue is not limited to small graphs. These larger cases use degree 64 and visited size 256:
CUDA_VISIBLE_DEVICES=0 ./rng_control 10000 128 64 256 283 20 "$repro_dir/large-failure"
CUDA_VISIBLE_DEVICES=0 ./rng_control 10000 128 64 256 1 20 "$repro_dir/large-control"At N=10,000, seed 283 makes the first point and starting point both 582. The failure case failed 20/20 and the control passed 20/20. Replacing dimension 128 with 1024 produced the same result.
These are deterministic controls, not estimates of a production failure rate. A separate serial C baseline, allowing the native RNG to advance naturally across builds with 250-449 vectors, failed 9/4,000 times. Replaying its RNG trajectory repeated the failures, so those repeated runs are not independent samples.
Expected behavior
A valid build should handle an empty reverse-edge batch without issuing a zero-block CUDA launch. The choice of insertion order and starting point should not cause this failure.
Environment details (please complete the following information):
- Environment location: bare-metal Linux, x86_64, glibc 2.39.
- GPU: NVIDIA RTX PRO 6000 Blackwell Server Edition; reproduced on two GPUs.
- NVIDIA driver: 595.71.05.
- Method of cuVS install: conda binaries, no cuVS source patches or rebuild.
- Initial environment:
libcuvs 26.12.00a12, buildcuda13_260911154958_c740818b;libraft 26.12.00a10. - Also reproduced in an isolated environment with
libcuvs 26.12.00a84, buildcuda13_261006202211_6cdea7e9, CUDA 13.2. Loaded-library paths were verified. The serial baseline failed 9/4,000 times; the controlled 10,000-vector/128-dimensional case failed 20/20 and its control passed 20/20. - The exact seeded permutation depends on the C library's
rand()implementation.
Additional context
The relevant cuVS path at the initial tested commit is:
- Insertion permutation and Vamana build use native
rand()for the insertion order and starting point. - Greedy search removes the self-neighbor.
- The reverse-edge destination map at vamana_build.cuh:459 receives
total_edges == 0.
A cuda-gdb capture at cudaLaunchKernelExC_ptsz showed the following (template names abbreviated):
caller: raft::linalg::detail::map_call<...>
caller: cuvs::neighbors::vamana::detail::batched_insert_vamana<...>
map length = 0
grid = (0, 1, 1)
block = (32, 1, 1)
Cuda Driver error detected: Grid Dimension X must be nonzero
Cuda Driver error detected: Returning 1 (CUDA_ERROR_INVALID_VALUE) from cuLaunchKernelEx_ptsz
CUDA_LAUNCH_BLOCKING=1 reproduced the same serial-baseline failures. Rebuilding the same vector data can succeed because cuVS's native process-global RNG state has advanced; retrying is therefore a mitigation, not a fix.
Related zero-size launch discussion: NVIDIA/raft#3104. This report provides a concrete Vamana caller and reproduction; it does not attribute the bug to that PR. A guard for empty work in the Vamana path and/or RAFT map seems worth investigating, but no fix has been validated here.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsIn Progress
Activity