Skip to content

Commit

Permalink
[BUG FIX] Add space type default and ef search parameter in warmup (#276
Browse files Browse the repository at this point in the history
)

Sets the default value of space type to L2 in KNNIndexShard.
KNNIndexShard is used during warmup to load segments into memory. For
indices created in ES 7.1 and 7.4, they will not have this value set
because the only space we supported was l2. So, we need to hardcode the
defaults here.

For nmslib, the ef_search parameter is configurable at load time. So, it
needs to be passed as a parameter in both the search load phase as well
as warmup. This commit adds it to the warmup phase and abstracts load
parameters to a helper function so that it can be consistent for both
search and warmup.

Signed-off-by: John Mazanec <jmazane@amazon.com>
  • Loading branch information
jmazanec15 committed Feb 10, 2022
1 parent 6bbc8e3 commit 2fb2ad1
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 16 deletions.
28 changes: 28 additions & 0 deletions src/main/java/org/opensearch/knn/index/IndexUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,23 @@

package org.opensearch.knn.index;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import org.opensearch.cluster.metadata.IndexMetadata;
import org.opensearch.cluster.metadata.MappingMetadata;
import org.opensearch.common.ValidationException;
import org.opensearch.knn.common.KNNConstants;
import org.opensearch.knn.index.util.KNNEngine;
import org.opensearch.knn.indices.ModelDao;
import org.opensearch.knn.indices.ModelMetadata;

import java.io.File;
import java.util.Collections;
import java.util.Map;

import static org.opensearch.knn.common.KNNConstants.BYTES_PER_KILOBYTES;
import static org.opensearch.knn.common.KNNConstants.HNSW_ALGO_EF_SEARCH;
import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE;

public class IndexUtil {

Expand Down Expand Up @@ -156,4 +162,26 @@ public static ValidationException validateKnnField(IndexMetadata indexMetadata,

return null;
}

/**
* Gets the load time parameters for a given engine.
*
* @param spaceType Space for this particular segment
* @param knnEngine Engine used for the native library indices being loaded in
* @param indexName Name of OpenSearch index that the segment files belong to
* @return load parameters that will be passed to the JNI.
*/
public static Map<String, Object> getParametersAtLoading(SpaceType spaceType, KNNEngine knnEngine, String indexName) {
Map<String, Object> loadParameters = Maps.newHashMap(ImmutableMap.of(
SPACE_TYPE, spaceType.getValue()
));

// For nmslib, we need to add the dynamic ef_search parameter that needs to be passed in when the
// hnsw graphs are loaded into memory
if (KNNEngine.NMSLIB.equals(knnEngine)) {
loadParameters.put(HNSW_ALGO_EF_SEARCH, KNNSettings.getEfSearchParam(indexName));
}

return Collections.unmodifiableMap(loadParameters);
}
}
9 changes: 6 additions & 3 deletions src/main/java/org/opensearch/knn/index/KNNIndexShard.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

package org.opensearch.knn.index;

import com.google.common.collect.ImmutableMap;
import org.apache.lucene.index.FieldInfo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand All @@ -31,6 +30,7 @@
import java.util.stream.Collectors;

import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE;
import static org.opensearch.knn.index.IndexUtil.getParametersAtLoading;
import static org.opensearch.knn.index.codec.util.KNNCodecUtil.buildEngineFileName;

/**
Expand Down Expand Up @@ -86,7 +86,7 @@ public void warmup() throws IOException {
new NativeMemoryEntryContext.IndexEntryContext(
key,
NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(),
ImmutableMap.of(SPACE_TYPE, value.getValue()),
getParametersAtLoading(value, KNNEngine.getEngineNameFromPath(key), getIndexName()),
getIndexName()
), true);
} catch (ExecutionException ex) {
Expand Down Expand Up @@ -122,7 +122,10 @@ private Map<String, SpaceType> getEnginePaths(IndexReader indexReader, KNNEngine

for (FieldInfo fieldInfo : reader.getFieldInfos()) {
if (fieldInfo.attributes().containsKey(KNNVectorFieldMapper.KNN_FIELD)) {
SpaceType spaceType = SpaceType.getSpace(fieldInfo.attributes().get(SPACE_TYPE));
// Space Type will not be present on ES versions 7.1 and 7.4 because the only available space type
// was L2. So, if Space Type is not present, just fall back to L2
String spaceTypeName = fieldInfo.attributes().getOrDefault(SPACE_TYPE, SpaceType.L2.getValue());
SpaceType spaceType = SpaceType.getSpace(spaceTypeName);
String engineFileName = buildEngineFileName(reader.getSegmentInfo().info.name,
knnEngine.getLatestBuildVersion(), fieldInfo.name, fileExtension);

Expand Down
16 changes: 3 additions & 13 deletions src/main/java/org/opensearch/knn/index/KNNWeight.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE;
import static org.opensearch.knn.common.KNNConstants.MODEL_ID;
import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE;
import static org.opensearch.knn.index.IndexUtil.getParametersAtLoading;
import static org.opensearch.knn.plugin.stats.KNNCounter.GRAPH_QUERY_ERRORS;

/**
Expand Down Expand Up @@ -135,24 +136,13 @@ public Scorer scorer(LeafReaderContext context) throws IOException {
KNNCounter.GRAPH_QUERY_REQUESTS.increment();

// We need to first get index allocation
NativeMemoryAllocation indexAllocation = null;

Map<String, Object> loadParameters = new HashMap<String, Object>() {{
put(SPACE_TYPE, spaceType.getValue());
}};

// For nmslib, we set efSearch from an index setting. This has the advantage of being able to dynamically
// update this value, which we cannot do at the moment for mapping parameters.
if (knnEngine.equals(KNNEngine.NMSLIB)) {
loadParameters.put(KNNConstants.HNSW_ALGO_EF_SEARCH, KNNSettings.getEfSearchParam(knnQuery.getIndexName()));
}

NativeMemoryAllocation indexAllocation;
try {
indexAllocation = nativeMemoryCacheManager.get(
new NativeMemoryEntryContext.IndexEntryContext(
indexPath.toString(),
NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(),
loadParameters,
getParametersAtLoading(spaceType, knnEngine, knnQuery.getIndexName()),
knnQuery.getIndexName()
), true);
} catch (ExecutionException e) {
Expand Down
72 changes: 72 additions & 0 deletions src/test/java/org/opensearch/knn/index/IndexUtilTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.knn.index;

import com.google.common.collect.ImmutableMap;
import org.opensearch.cluster.ClusterState;
import org.opensearch.cluster.metadata.IndexMetadata;
import org.opensearch.cluster.metadata.Metadata;
import org.opensearch.cluster.service.ClusterService;
import org.opensearch.common.settings.Settings;
import org.opensearch.knn.KNNTestCase;
import org.opensearch.knn.index.util.KNNEngine;

import java.util.Map;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.opensearch.knn.common.KNNConstants.HNSW_ALGO_EF_SEARCH;
import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE;
import static org.opensearch.knn.index.IndexUtil.getParametersAtLoading;
import static org.opensearch.knn.index.KNNSettings.KNN_ALGO_PARAM_EF_SEARCH;

public class IndexUtilTests extends KNNTestCase {
public void testGetLoadParameters() {
// Test faiss to ensure that space type gets set properly
SpaceType spaceType1 = SpaceType.COSINESIMIL;
KNNEngine knnEngine1 = KNNEngine.FAISS;
String indexName = "my-test-index";

Map<String, Object> loadParameters = getParametersAtLoading(spaceType1, knnEngine1, indexName);
assertEquals(1, loadParameters.size());
assertEquals(spaceType1.getValue(), loadParameters.get(SPACE_TYPE));

// Test nmslib to ensure both space type and ef search are properly set
SpaceType spaceType2 = SpaceType.L1;
KNNEngine knnEngine2 = KNNEngine.NMSLIB;
int efSearchValue = 413;

// We use the constant for the setting here as opposed to the identifier of efSearch in nmslib jni
Map<String, Object> indexSettings = ImmutableMap.of(
KNN_ALGO_PARAM_EF_SEARCH, efSearchValue
);

// Because ef search comes from an index setting, we need to mock the long line of calls to get those
// index settings
Settings settings = Settings.builder().loadFromMap(indexSettings).build();
IndexMetadata indexMetadata = mock(IndexMetadata.class);
when(indexMetadata.getSettings()).thenReturn(settings);
Metadata metadata = mock(Metadata.class);
when(metadata.index(anyString())).thenReturn(indexMetadata);
ClusterState clusterState = mock(ClusterState.class);
when(clusterState.getMetadata()).thenReturn(metadata);
ClusterService clusterService = mock(ClusterService.class);
when(clusterService.state()).thenReturn(clusterState);
KNNSettings.state().setClusterService(clusterService);

loadParameters = getParametersAtLoading(spaceType2, knnEngine2, indexName);
assertEquals(2, loadParameters.size());
assertEquals(spaceType2.getValue(), loadParameters.get(SPACE_TYPE));
assertEquals(efSearchValue, loadParameters.get(HNSW_ALGO_EF_SEARCH));
}
}

0 comments on commit 2fb2ad1

Please sign in to comment.