Summary
While validating vector-search score parity across Milvus Lite, Milvus server, and Zilliz Cloud, I found that milvus-lite==3.0 returns different raw distance values from Milvus server / Zilliz Cloud for the same MilvusClient.search() call.
This is especially visible for COSINE and L2:
COSINE: Milvus Lite 3.0 returns distance-style values (same=0.0, orthogonal=1.0), while Milvus server, Zilliz Cloud, and Milvus Lite 2.5 return similarity-style values (same=1.0, orthogonal=0.0).
L2: Milvus Lite 3.0 returns Euclidean distance for this probe (orthogonal~=1.4142135), while Milvus server, Zilliz Cloud, and Milvus Lite 2.5 return squared L2 distance (orthogonal=2.0).
If this is intentional, it would be useful to document the behavior and migration guidance. If not, it would be helpful to align Milvus Lite 3.0 with Milvus server / Zilliz Cloud, since client integrations often normalize scores based on the raw distance field.
Probe setup
2D data:
- query:
[1.0, 0.0]
- row 1:
[1.0, 0.0]
- row 2:
[0.0, 1.0]
All tests use MilvusClient.create_collection(..., dimension=2, metric_type=<metric>, auto_id=False) and then MilvusClient.search(..., search_params={"metric_type": <metric>}).
Observed results
| Backend |
Tested versions |
COSINE raw distance |
IP raw distance |
L2 raw distance |
| Milvus Lite 2.5 |
pymilvus==2.5.18, milvus-lite==2.5.1 |
same 1.0, orthogonal 0.0 |
same 1.0, orthogonal 0.0 |
same 0.0, orthogonal 2.0 |
| Milvus Lite 3.0 |
pymilvus==3.0.0, milvus-lite==3.0 |
same 0.0, orthogonal 1.0 |
same 1.0, orthogonal 0.0 |
same 0.0, orthogonal 1.4142135 |
| Milvus server 2.5 |
milvusdb/milvus:v2.5.13, pymilvus==3.0.0 |
same 1.0, orthogonal 0.0 |
same 1.0, orthogonal 0.0 |
same 0.0, orthogonal 2.0 |
| Milvus server 3.0 |
milvusdb/milvus:v3.0-beta, pymilvus==3.0.0 |
same 1.0, orthogonal 0.0 |
same 1.0, orthogonal 0.0 |
same 0.0, orthogonal 2.0 |
| Zilliz Cloud |
pymilvus==3.0.0 |
same 1.0, orthogonal 0.0 |
same 1.0, orthogonal 0.0 |
same 0.0, orthogonal 2.0 |
Note: for the Milvus Lite 2.5 probe, milvus-lite==2.5.1 imports pkg_resources, so the temporary test environment used setuptools<81.
Minimal reproduction
import tempfile
import uuid
from pathlib import Path
from importlib.metadata import version
from pymilvus import MilvusClient
print(f"pymilvus=={version('pymilvus')}")
try:
print(f"milvus-lite=={version('milvus-lite')}")
except Exception:
pass
# For Lite, use a local file URI. For server/Zilliz, replace with the service URI.
uri = str(Path(tempfile.mkdtemp()) / "probe.db")
client = MilvusClient(uri=uri)
try:
for metric in ["COSINE", "IP", "L2"]:
collection = f"tmp_metric_probe_{metric.lower()}_{uuid.uuid4().hex[:10]}"
try:
client.create_collection(
collection_name=collection,
dimension=2,
metric_type=metric,
auto_id=False,
)
client.insert(
collection_name=collection,
data=[
{"id": 1, "vector": [1.0, 0.0]},
{"id": 2, "vector": [0.0, 1.0]},
],
)
results = client.search(
collection_name=collection,
data=[[1.0, 0.0]],
anns_field="vector",
limit=2,
search_params={"metric_type": metric},
output_fields=["id"],
)
rows = []
for row in results[0]:
entity = row.get("entity") or {}
rows.append({
"id": row.get("id") or entity.get("id"),
"distance": row.get("distance"),
})
print(metric, rows)
finally:
if client.has_collection(collection):
client.drop_collection(collection)
finally:
client.close()
Expected behavior
The raw distance semantics should either:
- match Milvus server / Zilliz Cloud for the same metric, or
- be explicitly documented as a Milvus Lite 3.0 behavior change, including guidance for clients that need consistent score normalization across Lite, server, and Zilliz Cloud.
Summary
While validating vector-search score parity across Milvus Lite, Milvus server, and Zilliz Cloud, I found that
milvus-lite==3.0returns different rawdistancevalues from Milvus server / Zilliz Cloud for the sameMilvusClient.search()call.This is especially visible for
COSINEandL2:COSINE: Milvus Lite 3.0 returns distance-style values (same=0.0,orthogonal=1.0), while Milvus server, Zilliz Cloud, and Milvus Lite 2.5 return similarity-style values (same=1.0,orthogonal=0.0).L2: Milvus Lite 3.0 returns Euclidean distance for this probe (orthogonal~=1.4142135), while Milvus server, Zilliz Cloud, and Milvus Lite 2.5 return squared L2 distance (orthogonal=2.0).If this is intentional, it would be useful to document the behavior and migration guidance. If not, it would be helpful to align Milvus Lite 3.0 with Milvus server / Zilliz Cloud, since client integrations often normalize scores based on the raw
distancefield.Probe setup
2D data:
[1.0, 0.0][1.0, 0.0][0.0, 1.0]All tests use
MilvusClient.create_collection(..., dimension=2, metric_type=<metric>, auto_id=False)and thenMilvusClient.search(..., search_params={"metric_type": <metric>}).Observed results
distancedistancedistancepymilvus==2.5.18,milvus-lite==2.5.11.0, orthogonal0.01.0, orthogonal0.00.0, orthogonal2.0pymilvus==3.0.0,milvus-lite==3.00.0, orthogonal1.01.0, orthogonal0.00.0, orthogonal1.4142135milvusdb/milvus:v2.5.13,pymilvus==3.0.01.0, orthogonal0.01.0, orthogonal0.00.0, orthogonal2.0milvusdb/milvus:v3.0-beta,pymilvus==3.0.01.0, orthogonal0.01.0, orthogonal0.00.0, orthogonal2.0pymilvus==3.0.01.0, orthogonal0.01.0, orthogonal0.00.0, orthogonal2.0Note: for the Milvus Lite 2.5 probe,
milvus-lite==2.5.1importspkg_resources, so the temporary test environment usedsetuptools<81.Minimal reproduction
Expected behavior
The raw
distancesemantics should either: