Skip to content
Closed
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
6c508e3
DiskBBQ - adjust query greediness based on per segment affinity
tteofili Jul 31, 2025
6281bcf
wip fixes
tteofili Jul 31, 2025
62e4d0c
Merge branch 'main' of github.com:elastic/elasticsearch into diskbbq_…
tteofili Jul 31, 2025
ab11958
wip fixes
tteofili Jul 31, 2025
980940e
Merge branch 'main' of github.com:elastic/elasticsearch into diskbbq_…
tteofili Aug 1, 2025
3f4e5fd
Merge branch 'main' of github.com:elastic/elasticsearch into diskbbq_…
tteofili Aug 1, 2025
df0210d
Include top 2 parent scores into affinity, with larger segments
tteofili Aug 1, 2025
8cbad8f
minor fix
tteofili Aug 1, 2025
b097c20
include (parent) centroids scores for affinity, with larger segments
tteofili Aug 4, 2025
9499be1
Merge branch 'main' of github.com:elastic/elasticsearch into diskbbq_…
tteofili Aug 4, 2025
0635453
more sensible and generic threshold definition
tteofili Aug 4, 2025
d52dc40
[CI] Auto commit changes from spotless
Aug 4, 2025
c658856
Merge branch 'diskbbq_segment_affinity' of github.com:tteofili/elasti…
tteofili Aug 4, 2025
34cf456
spotless
tteofili Aug 4, 2025
0dc22eb
minor tweaks
tteofili Aug 4, 2025
028482e
Merge branch 'main' of github.com:elastic/elasticsearch into diskbbq_…
tteofili Aug 4, 2025
a6702eb
minor tweaks
tteofili Aug 4, 2025
5288b8e
[CI] Auto commit changes from spotless
Aug 4, 2025
0a74a0c
set visited vector max budget ratio
tteofili Aug 5, 2025
7600d50
Merge branch 'diskbbq_segment_affinity' of github.com:tteofili/elasti…
tteofili Aug 5, 2025
c3cec18
Merge branch 'main' of github.com:elastic/elasticsearch into diskbbq_…
tteofili Aug 5, 2025
4f98473
minor
tteofili Aug 5, 2025
72d6d24
minor tweaks
tteofili Aug 5, 2025
9ee4bfd
minor
tteofili Aug 5, 2025
4f9982e
Merge branch 'main' of github.com:elastic/elasticsearch into diskbbq_…
tteofili Aug 5, 2025
aeee542
minor tweaks, add knn tester param
tteofili Aug 5, 2025
5facc1b
[CI] Auto commit changes from spotless
Aug 5, 2025
feedbdf
minor tweaks
tteofili Aug 5, 2025
58a397b
Merge branch 'diskbbq_segment_affinity' of github.com:tteofili/elasti…
tteofili Aug 5, 2025
c3f97c9
minor tweaks
tteofili Aug 5, 2025
363d987
Merge branch 'main' of github.com:elastic/elasticsearch into diskbbq_…
tteofili Aug 5, 2025
1b42ca3
spotless
tteofili Aug 5, 2025
dfd9ba2
Update docs/changelog/132396.yaml
tteofili Aug 5, 2025
b5446a2
Merge branch 'main' into diskbbq_segment_affinity
john-wagster Aug 6, 2025
e38e513
Merge branch 'main' into fork/tteofili/diskbbq_segment_affinity
iverase Aug 11, 2025
e3e2091
spotless
iverase Aug 11, 2025
ec987f7
Merge branch 'main' into diskbbq_segment_affinity
iverase Aug 11, 2025
9337703
[DiskBBQ] Replace n_probe, related to the number of centroids with v…
iverase Aug 12, 2025
71021f0
iter
iverase Aug 12, 2025
651e359
doh
iverase Aug 12, 2025
0d51546
iterate leaves to get total budget
john-wagster Aug 12, 2025
36f0169
[CI] Auto commit changes from spotless
Aug 12, 2025
35dfb21
merging w ratio switchover, set default for ratio when creating strat…
john-wagster Aug 12, 2025
08011c5
merging w head
john-wagster Aug 12, 2025
ebbbfd9
clean up refs to nprobe, fixing how we instatiate strategy and defaul…
john-wagster Aug 13, 2025
baa3ebb
improved counting docs and set a min budget
john-wagster Aug 13, 2025
c9daf27
spotless
john-wagster Aug 13, 2025
ffc9a64
Merge branch 'main' into visitRatio
iverase Aug 13, 2025
d0019c2
iter
iverase Aug 13, 2025
528367a
Merge branch 'main' into visitRatio
iverase Aug 13, 2025
ab36139
Compute visitRatio globally when doing it dynamically
iverase Aug 13, 2025
b8b8091
Merge branch 'visitRatio' of github.com:iverase/elasticsearch into vi…
iverase Aug 13, 2025
5a7f5d8
merging w latest ratioVisit, fixed how we collect docs w vectors count
john-wagster Aug 13, 2025
cb1c444
spotless
john-wagster Aug 13, 2025
3d2720a
need to validate if scorer is null
john-wagster Aug 13, 2025
5b8443c
Merge branch 'main' into visitRatio
iverase Aug 13, 2025
66e11f7
cleanup
john-wagster Aug 13, 2025
b2b8bfe
fixing some calcs
john-wagster Aug 14, 2025
da21795
make expected depend on numVectors
iverase Aug 14, 2025
c80dcfc
assert we only support Float queries
iverase Aug 14, 2025
1583fa4
two fixes
benwtrent Aug 14, 2025
db1aa73
Merge branch 'visitRatio' of github.com:iverase/elasticsearch into vi…
benwtrent Aug 14, 2025
3b6f21d
merging w latest ratioVisit, slight adjustment to low affinity explor…
john-wagster Aug 14, 2025
15d92d5
merging main
john-wagster Aug 14, 2025
8ae7787
remove unnecessary scaling and visitRatio default duplicate logic
john-wagster Aug 14, 2025
ff4916d
calculating affinities accounting for some of the wide variance seen
john-wagster Aug 15, 2025
f3afcd6
calculating affinities accounting for some of the wide variance seen
john-wagster Aug 15, 2025
22ce362
don't add task with 0 budget, adjust thresholds for skewed affinities…
tteofili Aug 19, 2025
480fc0d
Merge branch 'main' of github.com:elastic/elasticsearch into diskbbq_…
tteofili Aug 19, 2025
e8dbdef
Merge branch 'main' of github.com:elastic/elasticsearch into diskbbq_…
tteofili Aug 20, 2025
d631243
comment
tteofili Aug 20, 2025
d261f02
remove explicit budget as it conflicts with visitedRatio, normalized …
tteofili Aug 22, 2025
e9d4d82
don't use affinity calculation for filters
tteofili Aug 22, 2025
3b291df
minor fix
tteofili Aug 22, 2025
76c22b9
more sensible visited ratio adjustment
tteofili Aug 22, 2025
7f2e954
removed useless interface
tteofili Aug 22, 2025
d887b88
Merge branch 'main' into diskbbq_segment_affinity
benwtrent Aug 25, 2025
e8e7d12
pull out magic nums, clean up, set hard cap on when to apply visit ra…
john-wagster Aug 25, 2025
a67fb67
Merge branch 'main' into diskbbq_segment_affinity
john-wagster Aug 26, 2025
daea67d
Merge branch 'main' of github.com:elastic/elasticsearch into diskbbq_…
tteofili Sep 3, 2025
571534c
merge
tteofili Sep 3, 2025
8e13a3a
[CI] Auto commit changes from spotless
Sep 3, 2025
3d1c6b6
account for numVectors for segment size variance being high, more dec…
tteofili Sep 5, 2025
daa8f40
work better with unbalanced segments, simplified
tteofili Sep 5, 2025
59cae53
Merge branch 'diskbbq_segment_affinity' of github.com:tteofili/elasti…
tteofili Sep 5, 2025
b9a9e23
changelog
tteofili Sep 5, 2025
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
6 changes: 6 additions & 0 deletions docs/changelog/132396.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 132396
summary: DiskBBQ - Adapt `nProbe` based on query - segment affinity in multi segment
scenario
area: Vector Search
type: enhancement
issues: []
34 changes: 24 additions & 10 deletions qa/vector/src/main/java/org/elasticsearch/test/knn/CmdLineArgs.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ record CmdLineArgs(
KnnIndexTester.IndexType indexType,
int numCandidates,
int k,
int[] nProbes,
double[] visitPercentages,
int ivfClusterSize,
int overSamplingFactor,
int hnswM,
Expand All @@ -53,7 +53,8 @@ record CmdLineArgs(
VectorEncoding vectorEncoding,
int dimensions,
boolean earlyTermination,
KnnIndexTester.MergePolicyType mergePolicy
KnnIndexTester.MergePolicyType mergePolicy,
float vectorsRatio
) implements ToXContentObject {

static final ParseField DOC_VECTORS_FIELD = new ParseField("doc_vectors");
Expand All @@ -63,7 +64,8 @@ record CmdLineArgs(
static final ParseField INDEX_TYPE_FIELD = new ParseField("index_type");
static final ParseField NUM_CANDIDATES_FIELD = new ParseField("num_candidates");
static final ParseField K_FIELD = new ParseField("k");
static final ParseField N_PROBE_FIELD = new ParseField("n_probe");
// static final ParseField N_PROBE_FIELD = new ParseField("n_probe");
static final ParseField VISIT_PERCENTAGE_FIELD = new ParseField("visit_percentage");
static final ParseField IVF_CLUSTER_SIZE_FIELD = new ParseField("ivf_cluster_size");
static final ParseField OVER_SAMPLING_FACTOR_FIELD = new ParseField("over_sampling_factor");
static final ParseField HNSW_M_FIELD = new ParseField("hnsw_m");
Expand All @@ -81,6 +83,7 @@ record CmdLineArgs(
static final ParseField FILTER_SELECTIVITY_FIELD = new ParseField("filter_selectivity");
static final ParseField SEED_FIELD = new ParseField("seed");
static final ParseField MERGE_POLICY_FIELD = new ParseField("merge_policy");
static final ParseField VECTORS_RATIO = new ParseField("vectors_ratio");

static CmdLineArgs fromXContent(XContentParser parser) throws IOException {
Builder builder = PARSER.apply(parser, null);
Expand All @@ -97,7 +100,8 @@ static CmdLineArgs fromXContent(XContentParser parser) throws IOException {
PARSER.declareString(Builder::setIndexType, INDEX_TYPE_FIELD);
PARSER.declareInt(Builder::setNumCandidates, NUM_CANDIDATES_FIELD);
PARSER.declareInt(Builder::setK, K_FIELD);
PARSER.declareIntArray(Builder::setNProbe, N_PROBE_FIELD);
// PARSER.declareIntArray(Builder::setNProbe, N_PROBE_FIELD);
PARSER.declareDoubleArray(Builder::setVisitPercentages, VISIT_PERCENTAGE_FIELD);
PARSER.declareInt(Builder::setIvfClusterSize, IVF_CLUSTER_SIZE_FIELD);
PARSER.declareInt(Builder::setOverSamplingFactor, OVER_SAMPLING_FACTOR_FIELD);
PARSER.declareInt(Builder::setHnswM, HNSW_M_FIELD);
Expand All @@ -115,6 +119,7 @@ static CmdLineArgs fromXContent(XContentParser parser) throws IOException {
PARSER.declareFloat(Builder::setFilterSelectivity, FILTER_SELECTIVITY_FIELD);
PARSER.declareLong(Builder::setSeed, SEED_FIELD);
PARSER.declareString(Builder::setMergePolicy, MERGE_POLICY_FIELD);
PARSER.declareFloat(Builder::setVectorsRatio, VECTORS_RATIO);
}

@Override
Expand All @@ -132,7 +137,8 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
builder.field(INDEX_TYPE_FIELD.getPreferredName(), indexType.name().toLowerCase(Locale.ROOT));
builder.field(NUM_CANDIDATES_FIELD.getPreferredName(), numCandidates);
builder.field(K_FIELD.getPreferredName(), k);
builder.field(N_PROBE_FIELD.getPreferredName(), nProbes);
// builder.field(N_PROBE_FIELD.getPreferredName(), nProbes);
builder.field(VISIT_PERCENTAGE_FIELD.getPreferredName(), visitPercentages);
builder.field(IVF_CLUSTER_SIZE_FIELD.getPreferredName(), ivfClusterSize);
builder.field(OVER_SAMPLING_FACTOR_FIELD.getPreferredName(), overSamplingFactor);
builder.field(HNSW_M_FIELD.getPreferredName(), hnswM);
Expand All @@ -149,6 +155,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
builder.field(EARLY_TERMINATION_FIELD.getPreferredName(), earlyTermination);
builder.field(FILTER_SELECTIVITY_FIELD.getPreferredName(), filterSelectivity);
builder.field(SEED_FIELD.getPreferredName(), seed);
builder.field(VECTORS_RATIO.getPreferredName(), vectorsRatio);
return builder.endObject();
}

Expand All @@ -165,7 +172,7 @@ static class Builder {
private KnnIndexTester.IndexType indexType = KnnIndexTester.IndexType.HNSW;
private int numCandidates = 1000;
private int k = 10;
private int[] nProbes = new int[] { 10 };
private double[] visitPercentages = new double[] { 1.0 };
private int ivfClusterSize = 1000;
private int overSamplingFactor = 1;
private int hnswM = 16;
Expand All @@ -183,6 +190,7 @@ static class Builder {
private float filterSelectivity = 1f;
private long seed = 1751900822751L;
private KnnIndexTester.MergePolicyType mergePolicy = null;
private float vectorsRatio = 1f;

public Builder setDocVectors(List<String> docVectors) {
if (docVectors == null || docVectors.isEmpty()) {
Expand Down Expand Up @@ -223,8 +231,8 @@ public Builder setK(int k) {
return this;
}

public Builder setNProbe(List<Integer> nProbes) {
this.nProbes = nProbes.stream().mapToInt(Integer::intValue).toArray();
public Builder setVisitPercentages(List<Double> visitPercentages) {
this.visitPercentages = visitPercentages.stream().mapToDouble(Double::doubleValue).toArray();
return this;
}

Expand Down Expand Up @@ -313,6 +321,11 @@ public Builder setMergePolicy(String mergePolicy) {
return this;
}

public Builder setVectorsRatio(float vectorsRatio) {
this.vectorsRatio = vectorsRatio;
return this;
}

public CmdLineArgs build() {
if (docVectors == null) {
throw new IllegalArgumentException("Document vectors path must be provided");
Expand All @@ -330,7 +343,7 @@ public CmdLineArgs build() {
indexType,
numCandidates,
k,
nProbes,
visitPercentages,
ivfClusterSize,
overSamplingFactor,
hnswM,
Expand All @@ -347,7 +360,8 @@ public CmdLineArgs build() {
vectorEncoding,
dimensions,
earlyTermination,
mergePolicy
mergePolicy,
vectorsRatio
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,18 +191,18 @@ public static void main(String[] args) throws Exception {
FormattedResults formattedResults = new FormattedResults();

for (CmdLineArgs cmdLineArgs : cmdLineArgsList) {
int[] nProbes = cmdLineArgs.indexType().equals(IndexType.IVF) && cmdLineArgs.numQueries() > 0
? cmdLineArgs.nProbes()
: new int[] { 0 };
double[] visitPercentages = cmdLineArgs.indexType().equals(IndexType.IVF) && cmdLineArgs.numQueries() > 0
? cmdLineArgs.visitPercentages()
: new double[] { 0 };
String indexType = cmdLineArgs.indexType().name().toLowerCase(Locale.ROOT);
Results indexResults = new Results(
cmdLineArgs.docVectors().get(0).getFileName().toString(),
indexType,
cmdLineArgs.numDocs(),
cmdLineArgs.filterSelectivity()
);
Results[] results = new Results[nProbes.length];
for (int i = 0; i < nProbes.length; i++) {
Results[] results = new Results[visitPercentages.length];
for (int i = 0; i < visitPercentages.length; i++) {
results[i] = new Results(
cmdLineArgs.docVectors().get(0).getFileName().toString(),
indexType,
Expand Down Expand Up @@ -240,8 +240,7 @@ public static void main(String[] args) throws Exception {
numSegments(indexPath, indexResults);
if (cmdLineArgs.queryVectors() != null && cmdLineArgs.numQueries() > 0) {
for (int i = 0; i < results.length; i++) {
int nProbe = nProbes[i];
KnnSearcher knnSearcher = new KnnSearcher(indexPath, cmdLineArgs, nProbe);
KnnSearcher knnSearcher = new KnnSearcher(indexPath, cmdLineArgs, visitPercentages[i]);
knnSearcher.runSearch(results[i], cmdLineArgs.earlyTermination());
}
}
Expand Down Expand Up @@ -293,7 +292,7 @@ public String toString() {
String[] searchHeaders = {
"index_name",
"index_type",
"n_probe",
"visit_percentage(%)",
"latency(ms)",
"net_cpu_time(ms)",
"avg_cpu_count",
Expand Down Expand Up @@ -324,7 +323,7 @@ public String toString() {
queryResultsArray[i] = new String[] {
queryResult.indexName,
queryResult.indexType,
Integer.toString(queryResult.nProbe),
String.format(Locale.ROOT, "%.2f", queryResult.visitPercentage),
String.format(Locale.ROOT, "%.2f", queryResult.avgLatency),
String.format(Locale.ROOT, "%.2f", queryResult.netCpuTimeMS),
String.format(Locale.ROOT, "%.2f", queryResult.avgCpuCount),
Expand Down Expand Up @@ -400,7 +399,7 @@ static class Results {
long indexTimeMS;
long forceMergeTimeMS;
int numSegments;
int nProbe;
double visitPercentage;
double avgLatency;
double qps;
double avgRecall;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class KnnSearcher {
private final float selectivity;
private final int topK;
private final int efSearch;
private final int nProbe;
private final double visitPercentage;
private final KnnIndexTester.IndexType indexType;
private int dim;
private final VectorSimilarityFunction similarityFunction;
Expand All @@ -116,7 +116,7 @@ class KnnSearcher {
private final int searchThreads;
private final int numSearchers;

KnnSearcher(Path indexPath, CmdLineArgs cmdLineArgs, int nProbe) {
KnnSearcher(Path indexPath, CmdLineArgs cmdLineArgs, double visitPercentage) {
this.docPath = cmdLineArgs.docVectors();
this.indexPath = indexPath;
this.queryPath = cmdLineArgs.queryVectors();
Expand All @@ -131,7 +131,7 @@ class KnnSearcher {
throw new IllegalArgumentException("numQueryVectors must be > 0");
}
this.efSearch = cmdLineArgs.numCandidates();
this.nProbe = nProbe;
this.visitPercentage = visitPercentage;
this.indexType = cmdLineArgs.indexType();
this.searchThreads = cmdLineArgs.searchThreads();
this.numSearchers = cmdLineArgs.numSearchers();
Expand Down Expand Up @@ -298,7 +298,7 @@ void runSearch(KnnIndexTester.Results finalResults, boolean earlyTermination) th
}
logger.info("checking results");
int[][] nn = getOrCalculateExactNN(offsetByteSize, filterQuery);
finalResults.nProbe = indexType == KnnIndexTester.IndexType.IVF ? nProbe : 0;
finalResults.visitPercentage = indexType == KnnIndexTester.IndexType.IVF ? visitPercentage : 0;
finalResults.avgRecall = checkResults(resultIds, nn, topK);
finalResults.qps = (1000f * numQueryVectors) / elapsed;
finalResults.avgLatency = (float) elapsed / numQueryVectors;
Expand Down Expand Up @@ -424,7 +424,8 @@ TopDocs doVectorQuery(float[] vector, IndexSearcher searcher, Query filterQuery,
}
int efSearch = Math.max(topK, this.efSearch);
if (indexType == KnnIndexTester.IndexType.IVF) {
knnQuery = new IVFKnnFloatVectorQuery(VECTOR_FIELD, vector, topK, efSearch, filterQuery, nProbe);
float visitRatio = (float) (visitPercentage / 100);
knnQuery = new IVFKnnFloatVectorQuery(VECTOR_FIELD, vector, topK, efSearch, filterQuery, visitRatio);
} else {
knnQuery = new ESKnnFloatVectorQuery(
VECTOR_FIELD,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,67 @@ CentroidIterator getCentroidIterator(FieldInfo fieldInfo, int numCentroids, Inde
return getCentroidIteratorNoParent(fieldInfo, centroids, numCentroids, scorer, quantized, queryParams, globalCentroidDp);
}

@Override
public float[] getCentroidsScores(FieldInfo fieldInfo, int numCentroids, IndexInput centroids, float[] targetQuery, boolean parents)
throws IOException {
final FieldEntry fieldEntry = fields.get(fieldInfo.number);
final float globalCentroidDp = fieldEntry.globalCentroidDp();
final OptimizedScalarQuantizer scalarQuantizer = new OptimizedScalarQuantizer(fieldInfo.getVectorSimilarityFunction());
final int[] scratch = new int[targetQuery.length];
float[] targetQueryCopy = ArrayUtil.copyArray(targetQuery);
if (fieldInfo.getVectorSimilarityFunction() == COSINE) {
VectorUtil.l2normalize(targetQueryCopy);
}
final OptimizedScalarQuantizer.QuantizationResult queryParams = scalarQuantizer.scalarQuantize(
targetQueryCopy,
scratch,
(byte) 7,
fieldEntry.globalCentroid()
);
final byte[] quantizedQuery = new byte[targetQuery.length];
for (int i = 0; i < quantizedQuery.length; i++) {
quantizedQuery[i] = (byte) scratch[i];
}
final ES92Int7VectorsScorer scorer = ESVectorUtil.getES92Int7VectorsScorer(centroids, fieldInfo.getVectorDimension());
centroids.seek(0L);
// final scores
final float[] scores = new float[ES92Int7VectorsScorer.BULK_SIZE];

int numParents = centroids.readVInt();
if (parents && numParents > 0) {
final NeighborQueue parentsQueue = new NeighborQueue(numParents, true);
final int maxChildrenSize = centroids.readVInt();
final NeighborQueue currentParentQueue = new NeighborQueue(maxChildrenSize, true);
final int bufferSize = (int) Math.max(numCentroids * CENTROID_SAMPLING_PERCENTAGE, 1);
final NeighborQueue neighborQueue = new NeighborQueue(bufferSize, true);
score(
parentsQueue,
numParents,
0,
scorer,
quantizedQuery,
queryParams,
globalCentroidDp,
fieldInfo.getVectorSimilarityFunction(),
scores
);
} else {
final NeighborQueue neighborQueue = new NeighborQueue(numCentroids, true);
score(
neighborQueue,
numCentroids,
0,
scorer,
quantizedQuery,
queryParams,
globalCentroidDp,
fieldInfo.getVectorSimilarityFunction(),
scores
);
}
return scores;
}

private static CentroidIterator getCentroidIteratorNoParent(
FieldInfo fieldInfo,
IndexInput centroids,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ public class IVFVectorsFormat extends KnnVectorsFormat {
);

// This dynamically sets the cluster probe based on the `k` requested and the number of clusters.
// useful when searching with 'efSearch' type parameters instead of requiring a specific nprobe.
public static final int DYNAMIC_NPROBE = -1;
// useful when searching with 'efSearch' type parameters instead of requiring a specific ratio.
public static final float DYNAMIC_VISIT_RATIO = 0.0f;
public static final int DEFAULT_VECTORS_PER_CLUSTER = 384;
public static final int MIN_VECTORS_PER_CLUSTER = 64;
public static final int MAX_VECTORS_PER_CLUSTER = 1 << 16; // 65536
Expand Down
Loading