Skip to main content

PathSeer: Adaptive Filtered Vector Search

· 15 min read

PathSeer: Adaptive Filtered Vector Search

PathSeer: adaptive filtered vector search


Introduction​

Most vector search use-cases have need for filtered vector search and it can be argued this is the most popular query type. A filtered vector search combines a semantic query such as books about architecture with structured criteria like published_date > 2000 or language = 'en'.

There are many ways to approach this problem with vector indices. One set of approaches are filter aware, you define what filters will be used upfront. Filter aware approaches perform very well on low-cardinality static data, but do not work well in a dynamic environment where filters (and even the schema) change over time. The other set of approaches (like ACORN) can be termed filter (or predicate) agnostic, and generally amount to different ways to explore the ANN neighbour graph.

In late 2024, we were one of the first vector databases to ship ACORN as an HNSW filter strategy, and it became the default in 1.34. ACORN performs particularly well when filters are negatively correlated, however it loses recall on very sparse filters, and above 40% selectivity its two-hop expansion costs more than it saves, so we restricted ACORN based on selectivity.

We felt there were still improvements available and so were pleasantly surprised when Zhiyue Li, Guangyan Zhang and colleagues at Tsinghua University announced their SIGMOD 2026 paper PathSeer: Adaptive Neighbor Handling for Efficient Filtered ANN Search. The paper describes PathSeer, a new predicate-agnostic filtered vector search algorithm in HNSW with large performance improvements over other strategies.

As part of their research, Zhiyue Li added PathSeer to Weaviate. In this blog we will go through the algorithm and showcase performance improvements, and how to use it in Weaviate 1.40.

How to handle a neighbor​

When using graph-based ANN techniques like HNSW, candidates are found using a beam search algorithm which determines the order in which new nodes get explored.

With unfiltered vector search this is relatively easy - just compute the distance calculation between the query vector and candidate vector. However with filtered vector search there are more decisions to make.

One important decision is: do you compute the distance first or check the filter first?

The PathSeer paper names these two options:

  • Distance first (DFN). Compute the distance to every neighbor and push it to the candidate heap. Check the filter when a neighbor is good enough to enter the result set. This is equivalent to Weaviate's sweeping strategy. It ensures the graph never disconnects, but at low selectivity most of the distance computations are wasted on nodes that don't pass the filter.
  • Filter first (DOS). Check the filter on every neighbor and only calculate distances for the ones that pass. This is in essence ACORN. It avoids wasted distance calculations but has potential to disconnect the graph leading to lower recall. One insight from ACORN is to use 2-hop neighbors, i.e. check if a neighbor matches the filter or any of its neighbors. It tends to work well in low-selectivity filters but at high selectivity the two hops multiply the number of filter checks needed.

The paper measured both strategies on six workloads and found the best choice varied by dataset, depending on how selective a filter was and how expensive the filter check was compared to the distance calculation.

In Weaviate we resolve the filter against the inverted index into a roaring bitmap before the vector search starts. This ensures that the filter check is just a bitmap lookup (around 17 ns), regardless of how complex the original filter was. The distance computation will cost roughly 50 - 110 ns dependent on quantization and size of the vector.

Introducing PathSeer​

PathSeer's answer to this is to decide the strategy per node dynamically.

In the paper PathSeer splits each node's neighbor list in two. The sparse zone has the usual pruned HNSW neighbors and is always explored distance first. The expanding zone has extra, unpruned neighbors sorted by distance. The expanding zone is walked filter first until a predefined budget is passed where it switches to distance first. The result is a mixture of filter first and distance first computations with no per query tuning.

In the authors' Faiss implementation this gave 1.2 to 13.5 times the throughput of plain HNSW, and 1.6 to 10.6 times the throughput of ACORN, at 90% recall@10, for selectivities from 100% down to 0.1%.

Zhiyue Li, one of the authors of the PathSeer paper created the original Weaviate PR, however with notable improvements to compatibility:

  • In the new approach, separate neighbor lists are no longer needed and PathSeer is compatible with normal HNSW indices.
  • This allows for filter strategies to be changed on an existing HNSW index - no rebuild required.

In the modified approach there are two phases:

  1. Two-hop burst. While the heap has fewer than ef entries, each popped node also adds its two hop neighbors that match the filter. This operates as the expanding zone, but is taken from the existing graph. It stops once the heap is full, so it does not multiply filter checks for the rest of the search.
  2. Once the heap is full, skip distances between non-matching nodes. When a popped node does not match the filter, its neighbors that also don't match are skipped, with no distance computed. When the popped node does match, all its neighbors get distances as in sweeping, matching or not. The second half is important: non-matching nodes next to matching ones stay in the search as bridges, so it can still cross regions where nothing matches. Skipped nodes are not marked visited, so another path can still reach them later.
PathSeer's two phases at layer zero: while the result heap is filling, every neighbour of a popped node gets a distance computation and matching two-hop neighbours are added; once the heap holds ef matches, a popped node that fails the filter skips its non-matching neighbours without a distance computationPathSeer's two phases at layer zero: while the result heap is filling, every neighbour of a popped node gets a distance computation and matching two-hop neighbours are added; once the heap holds ef matches, a popped node that fails the filter skips its non-matching neighbours without a distance computation

Benchmarks​

The below benchmarks used a Weaviate 1.40 build on a GCP c4-standard-16 (16 vCPUs, 60 GB Memory) instance. The Weaviate benchmarker tool was used, in particular with flat search cutoff disabled, so we could test the filtered ANN performance even when normally the filter would be restrictive enough to trigger a brute force search.

Three sources for filtered datasets are used.

  • BEIR Cohere. The sets from the ACORN post: 500k Cohere v3 embeddings mixed from the NQ and MS MARCO corpora, with a filter on the MS MARCO share, plus a 1.1M set of 22 BEIR corpora with a filter on the source corpus.
  • Sphere DPR. 1M 768-dimension embeddings with uniformly random categories.
  • wiki-dpr 10M. 10M Wiki passages from e5-base-v2 embeddings on Hugging Face, with 27 generated filters (random, topical, and conjunction) and exact filtered ground truth for each.

Topical filters​

First we use a filtered dataset using Cohere v3 embeddings from BEIR that we used previously to evaluate ACORN. We combine two datasets (NQ and MSMARCO) and vary the percentage to change the selectivity of the filter.

In this dataset PathSeer matches or beats both sweeping and ACORN at every selectivity range.

Throughput at recall@10 = 0.90 against filter selectivity from 20% to 90% for sweeping, acorn and pathseer on BEIR Cohere 500kThroughput at recall@10 = 0.90 against filter selectivity from 20% to 90% for sweeping, acorn and pathseer on BEIR Cohere 500k

Here is a QPS vs recall graph showing 20% selectivity (20% of the dataset matches the filter) and low correlation (the query vector is not part of the filter).

QPS vs recall@10 for sweeping, acorn and pathseer on BEIR Cohere 500k at 20% selectivityQPS vs recall@10 for sweeping, acorn and pathseer on BEIR Cohere 500k at 20% selectivity

Next, and importantly, here is a graph showing again at 20% selectivity what happens when the query vector is part of the filter region. This is the scenario where previously sweeping would outperform ACORN, however here PathSeer outperforms both.

QPS vs recall@10 for sweeping, acorn and pathseer on BEIR Cohere 500k at 20% selectivity with a filter correlated to the queryQPS vs recall@10 for sweeping, acorn and pathseer on BEIR Cohere 500k at 20% selectivity with a filter correlated to the query

On the wiki-dpr 10M dataset we see PathSeer clearly outperforms ACORN and also exceeds sweeping on topical filters (built here by clustering the article embeddings, so the matching passages form a region of the graph).

Throughput at recall@10 = 0.90 against selectivity from 0.1% to 50% for topical filters on wiki-dpr 10M, for sweeping, acorn and pathseerThroughput at recall@10 = 0.90 against selectivity from 0.1% to 50% for topical filters on wiki-dpr 10M, for sweeping, acorn and pathseer

Note that at 0.1% selectivity Weaviate would normally switch to a brute force search over the filter set (the default flatSearchCutoff is 40,000 objects), which is the better choice at that size.

Conjunction filters​

The conjunction filters are the ones closest to a production filter: a semantic attribute combined with a range on something orthogonal, such as a date or a tenant. On wiki-dpr each one is a topical filter AND a range on the object id, which breaks the link between where a passage sits in the embedding space and where it sits in the index. Six of them span 55k to 911k passages.

Throughput at recall@10 = 0.90 against selectivity for the six conjunction filters on wiki-dpr 10M, for sweeping, acorn and pathseerThroughput at recall@10 = 0.90 against selectivity for the six conjunction filters on wiki-dpr 10M, for sweeping, acorn and pathseer

PathSeer is the fastest strategy at 0.90 recall on all six, by 1.1 to 1.4 times over the better of sweeping and ACORN. ACORN reaches 0.90 on only three of them.

QPS vs recall@10 on wiki-dpr 10M with a conjunction filter of 238k passages (topical AND id range, 2.4% selectivity) for sweeping, acorn and pathseerQPS vs recall@10 on wiki-dpr 10M with a conjunction filter of 238k passages (topical AND id range, 2.4% selectivity) for sweeping, acorn and pathseer

On this 238k-passage conjunction PathSeer is 1.36 times sweeping at 0.90 recall and reaches 0.98 at ef 512, while ACORN tops out at 0.86.

QPS vs recall@10 on wiki-dpr 10M with a conjunction filter of 532k passages (topical AND id range, 5.3% selectivity) for sweeping, acorn and pathseerQPS vs recall@10 on wiki-dpr 10M with a conjunction filter of 532k passages (topical AND id range, 5.3% selectivity) for sweeping, acorn and pathseer

The larger conjunction, 532k passages at 5.3% selectivity, is dense enough for ACORN to reach 0.90 recall, and PathSeer is still ahead of both: 1.3 times sweeping and 1.7 times ACORN at 0.90, with a recall ceiling of 0.98 against ACORN's 0.93.

Random filters​

Random filters are where the filter has zero correlation with the query. They are the easiest test datasets to generate (just randomly assign each vector to a cluster based on the desired selectivity).

Here the filter carries no semantic information, so we can't deliberately move to a region with more vectors passing the filter, which most exposes ACORN. In these examples we see PathSeer degrade to sweeping, with an improvement at very low selectivity.

Throughput at recall@10 = 0.90 against selectivity from 0.1% to 50% for uniformly random filters on wiki-dpr 10M, for sweeping, acorn and pathseerThroughput at recall@10 = 0.90 against selectivity from 0.1% to 50% for uniformly random filters on wiki-dpr 10M, for sweeping, acorn and pathseer QPS vs recall@10 on wiki-dpr 10M with a uniform random filter of 400k passages (4% selectivity) for sweeping, acorn and pathseerQPS vs recall@10 on wiki-dpr 10M with a uniform random filter of 400k passages (4% selectivity) for sweeping, acorn and pathseer QPS vs recall@10 for sweeping, acorn and pathseer on Sphere DPR 1M with a uniformly random filter at 5% selectivityQPS vs recall@10 for sweeping, acorn and pathseer on Sphere DPR 1M with a uniformly random filter at 5% selectivity

Negatively correlated filters​

Finally the most difficult case is where the query is strongly negatively correlated with the filter. In this case, the distance calculations point the beam search in the wrong direction (towards better ANN results but fewer filter matches) and this has historically been a major reason for recommending ACORN.

Here we see ACORN still heavily outperforming sweeping, and also outperforming PathSeer, though by a much smaller margin.

QPS vs recall@10 for sweeping, acorn and pathseer on 1.1M BEIR Cohere vectors with a negatively correlated filter at about 4.5% selectivityQPS vs recall@10 for sweeping, acorn and pathseer on 1.1M BEIR Cohere vectors with a negatively correlated filter at about 4.5% selectivity

One property PathSeer keeps from sweeping is that recall continues to climb with ef. At ef 2048 PathSeer reaches 0.998 recall on this dataset while ACORN plateaus at 0.984, so for very high recall targets on a filter like this PathSeer is the graph strategy that gets there.

To put a number on "correlated", we measured for each dataset what share of a query's unfiltered top-10 neighbors pass the filter, divided by the filter's selectivity. A value of 1 means the filter is independent of the query, above 1 the filter favors the query's neighborhood, and below 1 it excludes it.

DatasetFilterSelectivityCorrelationFastest at 0.90 recall
BEIR Cohere 1.1Mquery's own corpus5.5%16.4pathseer (acorn within 1%)
wiki-dpr 10Mconjunction0.5% to 9%0.7 to 3.7pathseer, all six filters
wiki-dpr 10Mrandom0.1% to 50%1.0pathseer and sweeping within 7%
wiki-dpr 10Mtopical0.1% to 50%0.3 to 1.2pathseer, 1.0 to 1.4x sweeping
BEIR Cohere 500kMS MARCO share, NQ queries20%0.5pathseer, 1.24x acorn
BEIR Cohere 1.1Ma different corpus than the query4.5%0.07acorn, 8.7x pathseer

The wiki-dpr topical and random filters sit close to 1: the matching passages are simply sparse, not pushed away from the query. PathSeer is the fastest strategy at every correlation down to 0.5, and ACORN takes over only when the filter all but excludes the query's neighborhood.

Summary​

Queries per second at recall@10 = 0.90, interpolated between the two nearest ef points. "n/a" means the strategy did not reach 0.90 recall at any ef up to 512. wiki-dpr topical rows are the mean of the two variants.

FilterDatasetsweepingacornpathseerpathseer vs best of sweeping, acorn
randomSphere DPR 1M, 5% selectivity2509114434641.38x
randomSphere DPR 1M, 1% selectivity689n/a11931.73x
randomwiki-dpr 10M, 0.1% (10k)117n/a1761.50x
randomwiki-dpr 10M, 1% (100k)597n/a5580.93x
randomwiki-dpr 10M, 4% (400k)98337610471.07x
randomwiki-dpr 10M, 10% (1M)123676512230.99x
randomwiki-dpr 10M, 50% (5M)1562155215160.97x
topicalBEIR Cohere 500k, 20% selectivity, low correlation1700562669961.24x
topicalBEIR Cohere 500k, 30% selectivity, low correlation2423516766681.29x
topicalBEIR Cohere 500k, 40% selectivity, low correlation2681270769962.58x
topicalBEIR Cohere 500k, 50% selectivity, low correlation3893394874041.88x
topicalBEIR Cohere 500k, 70% selectivity, low correlation98259774100501.02x
topicalBEIR Cohere 500k, 90% selectivity, low correlation1001210110101471.00x
topicalBEIR Cohere 500k, 20% selectivity, high correlation7408803495211.19x
topicalBEIR Cohere 1.1M, ~4.5% selectivity, positive correlation8397910992141.01x
topicalwiki-dpr 10M, 0.1% (10k)50n/a701.40x
topicalwiki-dpr 10M, 0.4% (40k)108n/a1381.28x
topicalwiki-dpr 10M, 1% (100k)186n/a1941.04x
topicalwiki-dpr 10M, 4% (400k)6623747371.11x
topicalwiki-dpr 10M, 10% (1M)8845369591.08x
topicalwiki-dpr 10M, 20% (2M)123386511950.97x
topicalwiki-dpr 10M, 50% (5M)1308130013231.01x
conjunctionwiki-dpr 10M, 2.4% (238k)478n/a6491.36x
conjunctionwiki-dpr 10M, 5.3% (532k)6624978501.28x
negativeBEIR Cohere 1.1M, ~4.5% selectivity, negative correlation7918802160.12x

Using PathSeer​

PathSeer ships in Weaviate 1.40 as a value of the existing filterStrategy setting on the HNSW index. The default remains acorn.

"vectorIndexConfig": {
"filterStrategy": "pathseer"
}

The setting is mutable, so an existing collection can be switched without reindexing. The flatSearchCutoff setting still applies before any strategy: filters matching fewer objects than the cutoff are brute-forced as before.

With the Python client, set the strategy when creating a collection:

from weaviate.classes.config import Configure, VectorFilterStrategy

client.collections.create(
"Article",
vector_index_config=Configure.VectorIndex.hnsw(
filter_strategy=VectorFilterStrategy.PATHSEER,
),
)

Or switch an existing collection:

from weaviate.classes.config import Reconfigure, VectorFilterStrategy

articles = client.collections.use("Article")
articles.config.update(
vector_index_config=Reconfigure.VectorIndex.hnsw(
filter_strategy=VectorFilterStrategy.PATHSEER,
),
)

Conclusion​

PathSeer is the fastest filter strategy in Weaviate on almost every workload we tested, and it holds up where ACORN loses recall and where sweeping loses throughput. It needs no reindexing and no new parameters: set filterStrategy to pathseer on an existing collection and it takes effect immediately. Try it on your own filters in Weaviate 1.40 and let us know how it performs.

Ready to start building?​

Check out the Quickstart tutorial, or sign up for a free Weaviate Cloud account.

Don't want to miss another blog post?

Sign up for our bi-weekly newsletter to stay updated!

Follow us