Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
24b2570
Add MetalIVFPQImpl GPU-resident IVF list storage for PQ codes
Evandabest Jun 6, 2026
ba34b35
Add MetalIndexIVFPQ index class with add_core() fix
Evandabest Jun 6, 2026
cba7dd3
Add TestMetalIndexIVFPQ with L2, IP, reset, and round-trip tests
Evandabest Jun 7, 2026
4c9dce1
Fix PQ encoding to use residuals when by_residual is true
Evandabest Jun 8, 2026
c34e4e4
Add verifyPQSettings_ validation for constructor and train
Evandabest Jun 8, 2026
0723fd6
Add by_residual and polysemous_ht validation in copyFrom
Evandabest Jun 8, 2026
68ba2a1
Add MetalIndexIVFPQ and MetalIVFPQ to CMake build
Evandabest Jun 8, 2026
cd1ce45
Add TestMetalIndexIVFPQ to Metal CI build and test targets
Evandabest Jun 8, 2026
e7f89a6
Add IVFPQ Python tests for L2, IP, reset, and round-trip
Evandabest Jun 10, 2026
d803105
Merge branch 'main' into metal-ivfpq
Evandabest Jun 16, 2026
7a87246
Merge branch 'main' into metal-ivfpq
Evandabest Jun 22, 2026
1a5786f
Merge branch 'main' into metal-ivfpq
Evandabest Jul 21, 2026
be5e072
Add IVFPQ GPU scan and LUT-build kernels to MetalDistance.metal
Evandabest Jun 10, 2026
683c275
Add Metal IVFPQ lookup table and search kernels
Evandabest Jul 21, 2026
8e3b40d
Add Metal IVFPQ search orchestration and dispatch
Evandabest Jul 21, 2026
582b7a9
Enable GPU search in MetalIndexIVFPQ
Evandabest Jul 21, 2026
b3e4086
Forward IVFPQ search parameters to CPU fallbacks
Evandabest Jul 23, 2026
26d848b
Validate Metal IVFPQ layout before copyFrom
Evandabest Jul 23, 2026
7fa062c
Merge upstream main into metal-ivfpq-gpu-scan
Evandabest Jul 28, 2026
0b54ace
Merge branch 'main' into metal-ivfpq-gpu-scan
Evandabest Aug 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/build_cmake/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ runs:
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="$(brew --prefix libomp)" \
.
cmake --build build --target faiss faiss_metal swigfaiss TestMetalIndexFlat TestMetalIndexIVFFlat -j$(sysctl -n hw.logicalcpu)
cmake --build build --target faiss faiss_metal swigfaiss TestMetalIndexFlat TestMetalIndexIVFFlat TestMetalIndexIVFPQ -j$(sysctl -n hw.logicalcpu)
- name: C++ tests
if: inputs.metal != 'ON'
shell: bash
Expand Down
2 changes: 2 additions & 0 deletions faiss/gpu_metal/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ set(FAISS_METAL_SRC
MetalFlatKernels.mm
MetalIndexFlat.mm
MetalIndexIVFFlat.mm
MetalIndexIVFPQ.mm
impl/MetalIVFFlat.mm
impl/MetalIVFPQ.mm
StandardMetalResources.mm
MetalCloner.mm
MetalPythonBridge.mm
Expand Down
30 changes: 28 additions & 2 deletions faiss/gpu_metal/MetalCloner.mm
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
#import "MetalCloner.h"
#include <faiss/IndexFlat.h>
#include <faiss/IndexIVFFlat.h>
#include <faiss/IndexIVFPQ.h>
#include <faiss/impl/FaissAssert.h>
#include <cstring>
#import "MetalIndexFlat.h"
#import "MetalIndexIVFFlat.h"
#import "MetalIndexIVFPQ.h"
#import "StandardMetalResources.h"

namespace faiss {
Expand All @@ -35,6 +37,14 @@ int get_num_gpus() {
MetalIndexConfig config;
config.device = 0;

const auto* ivfPQ = dynamic_cast<const faiss::IndexIVFPQ*>(index);
if (ivfPQ) {
FAISS_THROW_IF_NOT(
ivfPQ->metric_type == METRIC_L2 ||
ivfPQ->metric_type == METRIC_INNER_PRODUCT);
return new MetalIndexIVFPQ(res->getResources(), ivfPQ, config);
}

const auto* ivfFlat = dynamic_cast<const faiss::IndexIVFFlat*>(index);
if (ivfFlat) {
FAISS_THROW_IF_NOT(
Expand Down Expand Up @@ -63,10 +73,26 @@ int get_num_gpus() {

FAISS_THROW_MSG(
"index_cpu_to_metal_gpu: unsupported index type "
"(supported: IndexFlat, IndexIVFFlat)");
"(supported: IndexFlat, IndexIVFFlat, IndexIVFPQ)");
}

faiss::Index* index_metal_gpu_to_cpu(const faiss::Index* index) {
const auto* metalIvfPQ = dynamic_cast<const MetalIndexIVFPQ*>(index);
if (metalIvfPQ) {
auto* quantizer = (metalIvfPQ->metric_type == METRIC_INNER_PRODUCT)
? (faiss::IndexFlat*)new faiss::IndexFlatIP(metalIvfPQ->d)
: (faiss::IndexFlat*)new faiss::IndexFlatL2(metalIvfPQ->d);
auto* cpu = new faiss::IndexIVFPQ(
quantizer,
metalIvfPQ->d,
metalIvfPQ->nlist(),
metalIvfPQ->getNumSubQuantizers(),
8);
cpu->own_fields = true;
metalIvfPQ->copyTo(cpu);
return cpu;
}

const auto* metalIvf = dynamic_cast<const MetalIndexIVFFlat*>(index);
if (metalIvf) {
auto* quantizer = (metalIvf->metric_type == METRIC_INNER_PRODUCT)
Expand All @@ -91,7 +117,7 @@ int get_num_gpus() {

FAISS_THROW_MSG(
"index_metal_gpu_to_cpu: unsupported index type "
"(supported: MetalIndexFlat, MetalIndexIVFFlat)");
"(supported: MetalIndexFlat, MetalIndexIVFFlat, MetalIndexIVFPQ)");
}

} // namespace gpu_metal
Expand Down
82 changes: 82 additions & 0 deletions faiss/gpu_metal/MetalDistance.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,87 @@ bool runMetalIVFFlatFullSearch(
bool centroidsAreFP16 = false,
bool waitForCompletion = true);

/// Full IVF-PQ search on the GPU: builds per-(query, probe) PQ lookup tables,
/// scans the assigned inverted lists with 8-bit ADC, and merges the per-list
/// top-k into the final top-k. Coarse quantization is performed on the CPU and
/// the assignments are passed in via @p coarseAssign.
bool runMetalIVFPQFullSearch(
id<MTLDevice> device,
id<MTLCommandQueue> queue,
id<MTLBuffer> queries,
id<MTLBuffer> coarseAssign,
id<MTLBuffer> coarseCentroids,
id<MTLBuffer> pqCentroids,
id<MTLBuffer> lookupTable,
id<MTLBuffer> codes,
id<MTLBuffer> ids,
id<MTLBuffer> listOffset,
id<MTLBuffer> listLength,
int nq,
int d,
int M,
int k,
int nprobe,
int nlist,
int avgListLen,
bool lookupFp16,
bool isL2,
id<MTLBuffer> outDistances,
id<MTLBuffer> outIndices,
id<MTLBuffer> perListDistBuf,
id<MTLBuffer> perListIdxBuf,
bool waitForCompletion = true);

/// Build the query-independent precomputed term of the IVFPQ L2 distance
/// decomposition on the GPU: term2[l][m][c] = ||pq[m][c]||^2 +
/// 2 <coarse_l[m], pq[m][c]>, laid out as nlist * M * 256 floats. Run once
/// per trained index; consumed by runMetalIVFPQPrecompSearch.
bool runMetalIVFPQPrecomputeTerm2(
id<MTLDevice> device,
id<MTLCommandQueue> queue,
id<MTLBuffer> coarseCentroids,
id<MTLBuffer> pqCentroids,
id<MTLBuffer> outTerm2,
int nlist,
int d,
int M);

/// IVF-PQ search using the precomputed-table decomposition: a per-query
/// M*256 term is built once per batch (instead of a full residual LUT per
/// (query, probe)), combined with @p term2 in threadgroup memory by the scan
/// kernel, and the coarse distance is added as the constant ||x - c||^2 term.
/// The scan keeps an exact running top-k over arbitrarily long lists and the
/// merge runs in rounds, so there is no list-length or nprobe*k cap; the
/// remaining requirements are k <= 512, M <= 16 and d/M <= 256.
/// @p term2 may be nil for inner product (qterm is the whole table);
/// @p useDis0 adds the coarse distance (required for L2, by_residual for IP).
bool runMetalIVFPQPrecompSearch(
id<MTLDevice> device,
id<MTLCommandQueue> queue,
id<MTLBuffer> queries,
id<MTLBuffer> coarseAssign,
id<MTLBuffer> coarseDist,
id<MTLBuffer> term2,
id<MTLBuffer> qtermScratch,
id<MTLBuffer> pqCentroids,
id<MTLBuffer> codes,
id<MTLBuffer> ids,
id<MTLBuffer> listOffset,
id<MTLBuffer> listLength,
int nq,
int d,
int M,
int k,
int nprobe,
bool isL2,
bool useDis0,
id<MTLBuffer> outDistances,
id<MTLBuffer> outIndices,
id<MTLBuffer> perListDistBuf,
id<MTLBuffer> perListIdxBuf,
id<MTLBuffer> mergeScratchDistBuf,
id<MTLBuffer> mergeScratchIdxBuf,
bool waitForCompletion = true);

} // namespace gpu_metal
} // namespace faiss
Loading
Loading