Skip to content

[BUG] Vamana build fails with cudaErrorInvalidValue on an empty reverse-edge batch #2767

Open

Description

@shaunakkapur

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

  1. Activate a conda environment with cuVS installed.
  2. Save the standalone program below as rng_control.c.
  3. 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(&params)!=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, build cuda13_260911154958_c740818b; libraft 26.12.00a10.
  • Also reproduced in an isolated environment with libcuvs 26.12.00a84, build cuda13_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:

  1. Insertion permutation and Vamana build use native rand() for the insertion order and starting point.
  2. Greedy search removes the self-neighbor.
  3. 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.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions