Skip to content

[Rust] IndexParams/SearchParams setters silently accept invalid values — add validated builder #1859

@zbennett10

Description

@zbennett10

Environment

  • cuVS: 26.2 (crates.io)
  • CUDA: 12.4
  • GPU: T4 (sm_75)
  • Rust: 1.82 (MSRV)

Problem

IndexParams setter methods return Self unconditionally. Invalid parameter
values are accepted at configuration time and only surface as an opaque CUDA
assertion inside Index::build() — after GPU memory has already been allocated
and graph construction has started.

// This compiles. It runs. It says nothing.
// ~1.8 seconds later, during Index::build():
let params = IndexParams::new()?
    .set_graph_degree(0)               // graph_degree=0 is invalid for CAGRA
    .set_intermediate_graph_degree(0)  // must be >= graph_degree
    .set_nn_descent_niter(0);          // must be > 0 for convergence

// Error surfaces here:
// cuvsError { code: CUVS_ERROR, text: "raft::exception: assertion failed" }
// No field name. No valid range. No hint.
let _index = Index::build(&res, &params, &dataset)?;

The same problem affects SearchParams (set_hashmap_max_fill_rate(1.5),
set_team_size(7)) and IndexParams for ivf_pq, ivf_flat, and vamana.

When CAGRA parameters are loaded from a config file at runtime, a
misconfiguration blocks the GPU thread for the full build duration before
producing an error that names neither the offending field nor its valid range.

Root Cause

The C API (cuvsCagraBuild) validates parameters on entry and returns
CUVS_ERROR on failure. The Rust bindings expose setters that write directly
to the FFI struct without pre-validation, so constraints documented in the C
header are invisible to callers until Index::build() fires.

Proposed Fix

Add IndexParams::builder() — a parallel, additive entry point that validates
all parameters in a single build() -> Result<IndexParams> call before any
FFI allocation occurs. Existing IndexParams::new()?.set_graph_degree(...) is
unchanged.

pub struct IndexParamsBuilder { /* private */ }

impl IndexParamsBuilder {
    pub fn graph_degree(mut self, v: usize) -> Self;
    pub fn intermediate_graph_degree(mut self, v: usize) -> Self;
    pub fn nn_descent_niter(mut self, v: usize) -> Self;
    // ... remaining fields

    /// Validate parameters in pure Rust (no GPU needed).
    pub fn validate(&self) -> Result<()>;

    /// Validate all parameters and allocate the FFI struct.
    ///
    /// Returns `Err` with a message naming the offending field and its valid
    /// range before any GPU work begins.
    pub fn build(self) -> Result<IndexParams>;
}

impl IndexParams {
    pub fn builder() -> IndexParamsBuilder;
}

Usage:

let params = IndexParams::builder()
    .graph_degree(32)
    .intermediate_graph_degree(64)
    .nn_descent_niter(20)
    .build()?;
// Err("graph_degree must be > 0; got 0") fires immediately,
// not 1.8s later inside Index::build()

Validation rules (derived from C header constraints):

  • graph_degree > 0; multiple of 32 preferred for warp alignment
  • intermediate_graph_degree >= graph_degree
  • nn_descent_niter > 0
  • itopk_size must be a power of 2
  • hashmap_max_fill_rate ∈ (0.1, 0.9)
  • team_size ∈ {0, 4, 8, 16, 32}

Backward Compatibility

The existing IndexParams::new() API and all setter methods are unchanged.
The builder is purely additive. No call sites break.

Scope

Rust bindings only. No C API changes required. The validation logic duplicates
constraints already enforced by the C layer — we are surfacing them earlier,
before the FFI call, with useful error messages.

A proof-of-concept implementation is available at:
https://github.com/zbennett10/cuvs/tree/feat/rust-validated-index-params-builder

Files

  • rust/cuvs/src/error.rs — add impl From<String> for Error
  • rust/cuvs/src/cagra/index_params.rsIndexParamsBuilder
  • rust/cuvs/src/cagra/search_params.rsSearchParamsBuilder
  • rust/cuvs/src/cagra/mod.rs — re-export builder types
  • Apply same pattern to ivf_pq, ivf_flat, vamana for consistency

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions