FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

feat: Make Milvus index and search params configurable by simonhearne · Pull Request #6917 · feast-dev/feast · GitHub

Repository navigation

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .md  (1) .py  (3) All 2 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
34 changes: 33 additions & 1 deletion docs/reference/online-stores/milvus.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,45 @@ online_store:
| `embedding_dim` | `128` | Dimension of vector fields. |
| `index_type` | `FLAT` | Index type for vector fields with `vector_index=True`. |
| `metric_type` | `COSINE` | Default metric when a field does not set `vector_search_metric`. |
| `nlist` | `128` | `nlist` index parameter. |
| `nlist` | `128` | `nlist` index parameter, used when `index_params` is unset. |
| `index_params` | unset | Index build parameters passed to Milvus, e.g. `{M: 16, efConstruction: 200}` for HNSW. Defaults to `{nlist: <nlist>}`, or no parameters for `AUTOINDEX`. |
| `search_params` | unset | Search parameters passed to Milvus, e.g. `{ef: 64}` for HNSW or `{level: 2}` for `AUTOINDEX`. Defaults to `{nprobe: 10}`, or no parameters for `AUTOINDEX`. |
| `vector_enabled` | `true` | Enables vector search. |
| `varchar_max_length` | `65535` | Default `max_length` of VARCHAR fields. Override per field with the `max_length` tag. |
| `enable_openai_compatible_store` | `false` | Store numeric features as native Milvus numeric types. |

The full set of configuration options is available in [MilvusOnlineStoreConfig](https://rtd.feast.dev/en/latest/#feast.infra.online_stores.milvus.MilvusOnlineStoreConfig).

## Index and search parameters

`index_type`, `index_params` and `search_params` are passed through to Milvus, so any index type the
server supports can be used. On Zilliz Cloud, `AUTOINDEX` is recommended; tune the recall/latency
trade-off with the `level` search parameter:

```yaml
online_store:
type: milvus
index_type: "AUTOINDEX"
search_params:
level: 2
```

For HNSW:

```yaml
online_store:
type: milvus
index_type: "HNSW"
index_params:
M: 16
efConstruction: 200
search_params:
ef: 64
```

Index parameters only apply when Feast creates a collection. To change them for an existing
collection, run `feast teardown` and `feast apply`, then materialize again.

## Collection loading

Feast creates collections together with their indexes, which makes Milvus load them straight away.
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,12 @@ class MilvusOnlineStoreConfig(FeastConfigBaseModel, VectorStoreConfig):
vector_enabled: Optional[bool] = True
text_search_enabled: Optional[bool] = False
nlist: Optional[int] = 128
# Index build params for vector fields, e.g. {"M": 16, "efConstruction": 200}.
# Defaults to {"nlist": nlist}, or {} for AUTOINDEX.
index_params: Optional[Dict[str, Any]] = None
# Search params, e.g. {"ef": 64}, or {"level": 2} for AUTOINDEX.
# Defaults to {"nprobe": 10}, or {} for AUTOINDEX.
search_params: Optional[Dict[str, Any]] = None
username: Optional[StrictStr] = ""
password: Optional[StrictStr] = ""
enable_openai_compatible_store: Optional[bool] = False
Expand Down Expand Up @@ -389,19 +395,17 @@ def _get_or_create_collection(
vector_field.name
].vector_search_metric
index_params.add_index(
collection_name=collection_name,
field_name=vector_field.name,
metric_type=metric or config.online_store.metric_type,
index_type=config.online_store.index_type,
index_name=f"vector_index_{vector_field.name}",
params={"nlist": config.online_store.nlist},
params=_index_build_params(config.online_store),
)
else:
# Vector fields that aren't searched (the placeholder,
# or arrays without vector_index) still need an index,
# otherwise Milvus servers refuse to load the collection.
index_params.add_index(
collection_name=collection_name,
field_name=vector_field.name,
metric_type="L2"
if vector_field.name == PLACEHOLDER_VECTOR_FIELD
Expand Down Expand Up @@ -848,7 +852,7 @@ def _combine_exprs(*parts: Optional[str]) -> Optional[str]:

search_params = {
"metric_type": distance_metric or config.online_store.metric_type,
"params": {"nprobe": 10},
"params": _search_params(config.online_store),
}

results = self.client.search(
Expand All @@ -865,7 +869,7 @@ def _combine_exprs(*parts: Optional[str]) -> Optional[str]:
# Vector search only
search_params = {
"metric_type": distance_metric or config.online_store.metric_type,
"params": {"nprobe": 10},
"params": _search_params(config.online_store),
}

results = self.client.search(
Expand Down Expand Up @@ -1028,6 +1032,27 @@ def _table_id(project: str, table: FeatureView, enable_versioning: bool = False)
return compute_table_id(project, table, enable_versioning)


def _is_autoindex(online_config: MilvusOnlineStoreConfig) -> bool:
return (online_config.index_type or "").upper() == "AUTOINDEX"


def _index_build_params(online_config: MilvusOnlineStoreConfig) -> Dict[str, Any]:
"""Build params for vector indexes. AUTOINDEX accepts none besides the metric."""
if online_config.index_params is not None:
return dict(online_config.index_params)
if _is_autoindex(online_config):
return {}
return {"nlist": online_config.nlist}


def _search_params(online_config: MilvusOnlineStoreConfig) -> Dict[str, Any]:
if online_config.search_params is not None:
return dict(online_config.search_params)
if _is_autoindex(online_config):
return {}
return {"nprobe": 10}


def _milvus_token(online_config: MilvusOnlineStoreConfig) -> str:
"""Return the token to authenticate with: ``token``, else ``username:password``."""
if online_config.token:
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,26 @@ def test_db_name(tmp_path: Path, project: str, store: MilvusOnlineStore) -> None
db_client = MilvusClient(uri=ZILLIZ_URI, token=ZILLIZ_TOKEN, db_name=db_name)
assert collection_name in db_client.list_collections()
assert collection_name not in admin.list_collections()


def test_autoindex_with_search_level(
tmp_path: Path, project: str, store: MilvusOnlineStore
) -> None:
config = _repo_config(
tmp_path, project, index_type="AUTOINDEX", search_params={"level": 2}
)
fv = _vector_feature_view()
store.update(config, [], [fv], [], [], partial=False)
_write_rows(store, config, fv, _vector_rows())

assert store.client is not None
index = store.client.describe_index(
f"{project}_{fv.name}", "vector_index_embedding"
)
assert index["index_type"] == "AUTOINDEX"

hits = _eventually(
lambda: _search(store, config, fv, [1.0, 0.0]),
lambda hits: len(hits) == 1,
)
assert hits[0]["city"].string_val == "Paris"
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from feast.protos.feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto
from feast.protos.feast.types.Value_pb2 import Value as ValueProto
from feast.repo_config import RepoConfig
from feast.types import Float32, Int64, String
from feast.types import Array, Float32, Int64, String
from feast.value_type import ValueType

MILVUS_MODULE = "feast.infra.online_stores.milvus_online_store.milvus"
Expand Down Expand Up @@ -375,3 +375,124 @@ def test_lite_path_used_without_uri(mock_client_cls: MagicMock) -> None:
MilvusOnlineStore()._connect(config)

mock_client_cls.assert_called_once_with("/tmp/online_store.db")


def _vector_feature_view(name: str = "driver_embeddings") -> FeatureView:
return FeatureView(
name=name,
entities=[_driver_entity()],
ttl=timedelta(days=1),
schema=[
Field(name="driver_id", dtype=Int64),
Field(
name="embedding",
dtype=Array(Float32),
vector_index=True,
vector_search_metric="COSINE",
),
Field(name="city", dtype=String),
],
)


def _vector_rows() -> Dict[int, Dict[str, ValueProto]]:
def embedding(x: float, y: float) -> ValueProto:
value = ValueProto()
value.float_list_val.val.extend([x, y])
return value

return {
1: {"embedding": embedding(1.0, 0.0), "city": ValueProto(string_val="Paris")},
2: {"embedding": embedding(0.0, 1.0), "city": ValueProto(string_val="Rome")},
}


def _search_with_mock(**online_store: Any) -> Dict[str, Any]:
"""Create a collection and search it with a mocked client.

Returns the index definition of the embedding field and the search kwargs.
"""
with patch(f"{MILVUS_MODULE}.MilvusClient") as mock_client_cls:
mock_client = _mock_client(mock_client_cls, has_collection=False)
mock_client.describe_collection.return_value = {
"collection_name": "test_milvus_driver_embeddings",
"fields": [
{"name": "driver_id_pk", "type": DataType.VARCHAR, "params": {}},
{"name": "event_ts", "type": DataType.INT64, "params": {}},
{"name": "created_ts", "type": DataType.INT64, "params": {}},
{
"name": "embedding",
"type": DataType.FLOAT_VECTOR,
"params": {"dim": 2},
},
{"name": "city", "type": DataType.VARCHAR, "params": {}},
],
}
mock_client.search.return_value = [[]]

store = MilvusOnlineStore()
store.retrieve_online_documents_v2(
_mock_config(embedding_dim=2, **online_store),
_vector_feature_view(),
["embedding", "city"],
embedding=[1.0, 0.0],
top_k=1,
)
return {
"index": _created_indexes(mock_client)["embedding"],
"search": mock_client.search.call_args.kwargs,
}


def test_default_index_and_search_params_unchanged() -> None:
result = _search_with_mock(index_type="IVF_FLAT")

assert result["index"]["index_type"] == "IVF_FLAT"
assert result["index"]["nlist"] == 128
assert result["search"]["search_params"]["params"] == {"nprobe": 10}


def test_autoindex_gets_no_index_or_search_params_by_default() -> None:
result = _search_with_mock(index_type="AUTOINDEX")

# Milvus servers reject AUTOINDEX with any build param besides the metric.
assert result["index"] == {
"field_name": "embedding",
"index_type": "AUTOINDEX",
"index_name": "vector_index_embedding",
"metric_type": "COSINE",
}
assert result["search"]["search_params"]["params"] == {}


def test_index_and_search_params_pass_through() -> None:
result = _search_with_mock(
index_type="HNSW",
index_params={"M": 16, "efConstruction": 200},
search_params={"ef": 64},
)

assert result["index"]["M"] == 16
assert result["index"]["efConstruction"] == 200
assert "nlist" not in result["index"]
assert result["search"]["search_params"]["params"] == {"ef": 64}


def test_autoindex_search_with_level(tmp_path: Path) -> None:
config = _lite_config(tmp_path, index_type="AUTOINDEX", search_params={"level": 2})
fv = _vector_feature_view()
store = MilvusOnlineStore()
store.update(config, [], [fv], [], [], partial=False)
_write_rows(store, config, fv, _vector_rows())

results = store.retrieve_online_documents_v2(
config,
fv,
["embedding", "city"],
embedding=[1.0, 0.0],
top_k=1,
distance_metric="COSINE",
)

assert len(results) == 1
assert results[0][2] is not None and results[0][2]["city"].string_val == "Paris"
Loading

Back | FazBrowse Home | New Git URL