Skip to content

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

Open
mnorris11 wants to merge 1 commit into
NVIDIA:mainfrom
mnorris11:cagra-optimize-device-overload
Open

[REVIEW] Add device-memory overload for cagra::helpers::optimize#2423
mnorris11 wants to merge 1 commit 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?

`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.
@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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant