Skip to content

[REVIEW] Add device-memory overload for cagra::helpers::optimize - #2423

Open
mnorris11 wants to merge 2 commits into
NVIDIA:mainfrom
mnorris11:cagra-optimize-device-overload
Open

[REVIEW] Add device-memory overload for cagra::helpers::optimize#2423
mnorris11 wants to merge 2 commits into
NVIDIA:mainfrom
mnorris11:cagra-optimize-device-overload

Conversation

@mnorris11

@mnorris11 mnorris11 commented Aug 6, 2026

Copy link
Copy Markdown

Disclaimer: AI mostly generated, but validated on a 8x H100 host. Let me know if I should delete the lengthy AI comments or if this PR is silly and there's better workarounds. We will add a hack in Faiss at facebookresearch/faiss#5500 until we can merge this PR / import the latest cuVS version (probably around 26.10? or later?).

  1. Please write a description in this text box of the changes that are being
    made.

Problem

cagra::helpers::optimize currently accepts host matrices only, so a caller that already holds its k-NN graph in device memory -- for example the output of all_neighbors::build -- must copy the graph to host, optimize, and copy back.

The device implementation already exists. graph::optimize is templated on both mdspan accessors and make_reverse_graph_gpu has an is_device_accessible fast path (graph_core.cuh:826), and batch_load_iterator switches to a zero-copy kPassthrough mode for device accessors. That code is unreachable today because detail::optimize in cagra_build.cuh erases the caller's accessor:

using g_accessor_internal =
  raft::host_device_accessor<cuda::std::default_accessor<internal_IdxT>,
                             raft::memory_type::host>;

so graph::optimize is only ever instantiated with host accessors. With host accessors the reverse-graph phase degrades into graph_degree separate host column gathers, each with its own H2D copy and a full stream synchronisation.

This change

  • propagates the caller's memory types through detail::optimize instead of erasing them, and makes new_graph accessor-generic there and in cagra::optimize;
  • adds a device_matrix_view overload of cagra::helpers::optimize to the public API;
  • exposes the existing guarantee_connectivity flag on the public overloads, which previously could not be reached from outside.

Both public overloads keep their existing signatures via a defaulted argument, so this is source compatible.

Measured on 100M x 128d vectors (graph degree 32, intermediate degree 32) on 8x H100, as part of a multi-GPU CAGRA build: the optimize step goes from 119.1s to 1.65s, a 72x reduction, with recall unchanged.

  1. Please ensure that you have written units tests for the changes made/features
    added.

Tests: adds a device-to-device case and a case asserting the device overload produces the same graph as the host overload for the same input.

  1. If you are closing an issue please use one of the automatic closing words as
    noted here: https://help.github.com/articles/closing-issues-using-keywords/

None I think?

@copy-pr-bot

copy-pr-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@mfoerste4 mfoerste4 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mnorris11 , thanks for the PR. Besides the lengthy AI comments you already mentioned the PR looks good to me. I would like to wait for other peoples comments on exposing the mst graph option in its current state though. CC @cjnolet

Comment thread cpp/include/cuvs/neighbors/cagra.hpp Outdated
raft::host_matrix_view<uint32_t, int64_t, raft::row_major> knn_graph,
raft::host_matrix_view<uint32_t, int64_t, raft::row_major> new_graph);
raft::host_matrix_view<uint32_t, int64_t, raft::row_major> new_graph,
bool guarantee_connectivity = false);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this parameter really required for your workflow? The mst path should be functionally correct, but is not optimized wrt. speed and memory consumption. I would rather not see it in the public API. @achirkin , what is your opinion on this?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this parameter will be a big problem since the default is false, but I agree unless you sure you need this feature it will be easier for us to support the API without it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do absolutely want the guarantee_connectivity to be exposed to the public. The reason this parameter was introduced was not for testing, but rather because we came to realization that some types of neighborhood graphs, such as the RNG graph which underlines assumptions in Vamana, implicitly assume the graph is connected. This can obviously greatly impact the navigability since NSG-style graphs tend to start with an all-neighbors knn graph (which is not guaranteed to be connected).

Comment thread cpp/src/neighbors/detail/cagra/cagra_build.cuh Outdated
Michael Norris added 2 commits August 13, 2026 10:10
`cagra::helpers::optimize` currently accepts host matrices only, so a caller
that already holds its k-NN graph in device memory -- for example the output of
`all_neighbors::build` -- must copy the graph to host, optimize, and copy back.

The device implementation already exists. `graph::optimize` is templated on both
mdspan accessors and `make_reverse_graph_gpu` has an `is_device_accessible` fast
path (graph_core.cuh:826), and `batch_load_iterator` switches to a zero-copy
`kPassthrough` mode for device accessors. That code is unreachable today because
`detail::optimize` in cagra_build.cuh erases the caller's accessor:

    using g_accessor_internal =
      raft::host_device_accessor<cuda::std::default_accessor<internal_IdxT>,
                                 raft::memory_type::host>;

so `graph::optimize` is only ever instantiated with host accessors. With host
accessors the reverse-graph phase degrades into `graph_degree` separate host
column gathers, each with its own H2D copy and a full stream synchronisation.

This change:

- propagates the caller's memory types through `detail::optimize` instead of
  erasing them, and makes `new_graph` accessor-generic there and in
  `cagra::optimize`;
- adds a `device_matrix_view` overload of `cagra::helpers::optimize` to the
  public API;
- exposes the existing `guarantee_connectivity` flag on the public overloads,
  which previously could not be reached from outside.

Both public overloads keep their existing signatures via a defaulted argument,
so this is source compatible.

Measured on 100M x 129d vectors (graph degree 32, intermediate degree 32) on
8x H100, as part of a multi-GPU CAGRA build: the optimize step goes from 119.1s
to 1.65s, a 72x reduction, with recall unchanged. At that scale it takes the
whole build->serialize pipeline from 8.0 to 5.4 minutes.

Tests: adds a device-to-device case and a case asserting the device overload
produces the same graph as the host overload for the same input.
@mnorris11
mnorris11 force-pushed the cagra-optimize-device-overload branch from 1ca817d to 1fe0cff Compare August 13, 2026 17:29
@cjnolet cjnolet added improvement Improves an existing functionality non-breaking Introduces a non-breaking change labels Aug 13, 2026
@cjnolet cjnolet moved this to In Progress in Unstructured Data Processing Aug 13, 2026

@mfoerste4 mfoerste4 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

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

Labels

improvement Improves an existing functionality non-breaking Introduces a non-breaking change

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

4 participants