Binary-quantized Euclidean query results are not ordered by reported score when rescore=false
Current Behavior
With Qdrant v1.19.0, an Euclidean nearest-neighbor query using binary quantization and quantization.rescore=false can return results whose reported score values are not ordered from smaller to larger.
Typical output:
ids: [4, 0, 1, 2, 3]
scores: [1.4142135, 0.0, 0.0, 1.4142135, 1.4142135]The result with score 1.4142135 appears before results with score 0.0.
The issue reproduces repeatedly on a fresh collection. It does not reproduce when rescore=true or when quantization is ignored.
Steps to Reproduce
- Start Qdrant v1.19.0:
docker run --rm --name qdrant-quant-order-repro \
-p 127.0.0.1:16353:6333 \
qdrant/qdrant:v1.19.0- Install the Python dependency:
pip install requests==2.34.2- Run this standalone Python script:
import time
import uuid
import requests
BASE = "http://127.0.0.1:16353"
COLLECTION = "quant_order_" + uuid.uuid4().hex[:8]
SEED = [
[-0.0494680889, -1.5822200775],
[-0.5807184577, -1.3024002314],
[-0.2222844660, 1.0905684233],
[-0.5575756431, 0.6532790065],
[ 0.7734391689, -0.2747519016],
]
# Update point 1 by changing only coordinate 0.
UPDATED_POINT_1 = [-0.2057184577, -1.3024002314]
QUERY = [1.3748825788, -1.0415413379]
def call(method, path, **kwargs):
response = requests.request(
method,
BASE + path,
timeout=30,
**kwargs,
)
response.raise_for_status()
return response.json()
def search(params):
points = call(
"POST",
f"/collections/{COLLECTION}/points/query",
json={
"query": QUERY,
"limit": 5,
"params": params,
},
)["result"]["points"]
return [
(int(point["id"]), float(point["score"]))
for point in points
]
def is_monotonic(rows):
scores = [score for _, score in rows]
return all(
left <= right + 2e-5
for left, right in zip(scores, scores[1:])
)
try:
call(
"PUT",
f"/collections/{COLLECTION}",
json={
"vectors": {
"size": 2,
"distance": "Euclid",
"hnsw_config": {
"m": 4,
"ef_construct": 64,
},
},
"quantization_config": {
"binary": {
"always_ram": True,
},
},
},
)
call(
"PUT",
f"/collections/{COLLECTION}/points?wait=true",
json={
"points": [
{"id": i, "vector": vector}
for i, vector in enumerate(SEED)
]
},
)
call(
"PUT",
f"/collections/{COLLECTION}/points/vectors?wait=true",
json={
"points": [
{"id": 1, "vector": UPDATED_POINT_1}
]
},
)
time.sleep(1)
quantized_params = {
"exact": False,
"hnsw_ef": 256,
"quantization": {
"rescore": False,
},
}
for attempt in range(3):
rows = search(quantized_params)
print(f"quantized run {attempt + 1}: {rows}")
# Euclidean results should be ordered from smaller
# to larger reported distance.
assert not is_monotonic(rows), (
"The anomaly was not reproduced: "
f"{rows}"
)
rescored = search({
"exact": False,
"hnsw_ef": 256,
"quantization": {
"rescore": True,
"oversampling": 4.0,
},
})
print("rescore=true:", rescored)
assert is_monotonic(rescored)
exact = search({
"exact": True,
"quantization": {
"ignore": True,
},
})
print("quantization ignored:", exact)
assert is_monotonic(exact)
print("BUG REPRODUCED")
finally:
requests.delete(
f"{BASE}/collections/{COLLECTION}",
timeout=30,
)Expected Behavior
For an Euclidean nearest-neighbor query, returned results should be ordered by their reported distance:
score[i] <= score[i + 1]Quantization-related approximation error and recall changes are acceptable, but the returned list should not violate the ordering implied by its own reported scores.
Possible Solution
Ensure that the final results from the binary-quantized, non-rescored search path are sorted by the reported Euclidean score before being returned.
A regression test should verify that the returned score sequence is monotonic when:
distance = Euclid
quantization = binary
exact = false
quantization.rescore = falseDetailed Description
The test creates a two-dimensional Euclidean collection with binary quantization enabled. Five points are inserted, and one stored vector is updated before querying.
With rescore=false, Qdrant returns quantized scores such as:
[1.4142135, 0.0, 0.0, 1.4142135, 1.4142135]For Euclidean distance, smaller scores represent closer points. Therefore, a result with score 1.4142135 should not precede results with score 0.0.
The same collection and query return monotonic scores when rescoring is enabled or quantization is ignored.
Possible Implementation
- Add an integration regression test using the minimal reproducer above.
- Inspect the binary-quantized search path used when
rescore=false. - Ensure the final returned Top-k list is sorted by the score reported in the response.
- Verify the behavior for both fresh points and updated vectors.
- Keep the existing approximation and recall behavior unchanged unless the ordering contract requires otherwise.
Source: qdrant/qdrant