[test](lance) Add indexed coverage for cosine, dot and non-Float32 element types - #67039
Conversation
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
af9fa35 to
54b999f
Compare
54b999f to
75e20b8
Compare
|
@zhangstar333 @Gabriel39 PTAL |
|
run buildall |
|
@FANNG1 failed one test in test_lance_vector_search_indexed_element_types |
|
@zhangstar333 Thanks for running it. I can't read the failure myself — the TeamCity instance asks for a login — so could you paste the assertion block from the log? The part I need is: The tag name alone would probably be enough to pin it down. For context on where I'd expect trouble: that suite covers three tables, and only the Float16 one records values that are not exactly representable.
Both are exact integers on any platform. The Float16 blocks are different — they record float32 cosine distances printed to 7-8 significant digits ( I regenerated the goldens on macOS/arm64 after rebuilding the fixture. If the failing tag is If it is a different tag, that guess is wrong and I will dig into it from the log. |
Exception: @FANNG1 seems everyone could login as guest for TeamCity. no need admin account. u could have a try |
Got it, thanks! |
…e and metric The Lance vector suites were all Float32 + L2. Doris only plans an indexed split when the query metric equals the metric the index was built with (LanceScanNode.metricMatches), so the cosine and dot branches of that comparison had never run against a real index, and no indexed table existed for any element type other than Float32. Two tiers now cover the whole matrix. Twelve tables carry it in depth: all six algorithms on Float32 + L2, plus cosine and dot, plus Float64, Float16 and UInt8. Each has goldens, a closed-form distance ladder where the data shape allows one, and a discriminator that proves the search parameter reached the index rather than falling back to a scan. Their data shape is per metric, because one shape cannot serve all of them - the collinear ladder is degenerate under cosine (directions converge, the top distances collapse to zero) and under dot (the answer ignores the query), so those tables use a directional shape, and UInt8 uses a thermometer code that makes hamming an exact ladder. One table, doris.vs_index_matrix, carries the remaining 44 cells at 64 rows, one vector column per cell. One column per cell rather than several indexes on one column, because only the first index built on a column is reachable - Lance answers the others with a silent brute-force scan, and Doris gets there by another route, since selectIndexSegments keeps only the segments of the first index it finds for the column's field id. Each cell is asserted to plan an indexed split with no fragment left scanned unindexed, to answer differently at nprobes=1 than at nprobes=4 (a scan has no partitions, so this is what distinguishes a real indexed search from a fallback), and to return what an exhaustive scan returns once its candidates are reranked. No goldens, so a fixture rebuild leaves it untouched. The generator's self-check is the whole contract for a fixture whose bytes are not reproducible, so it now pins a digest of every profile's vectors, reads rows back from each table, asserts the metric each index was actually built with (read from stats["indices"][*]["metric_type"], not searched for - a cosine IVF_PQ reports l2 on its sub-index), keeps the indexed-vs-flat comparison bit-exact for the integer ladders, and enforces the pylance and lance-namespace pins before building or verifying. Discriminator margins are reported and warned on when thin, and --repin lets a rebuild complete so those rows can be re-measured against the fixture that was actually promoted. buildable_combos() is a claim about Lance, so it was checked against Lance: all 96 cells of the matrix were built one per subprocess, 56 succeeded, and those 56 are exactly what it yields. The 40 failures are documented next to it and deliberately not attempted at build time - eighteen are Rust panics, and this script writes the fixture that gets committed. Finally, the fixture is published with `mc mirror --remove` instead of `mc cp -r`. The __manifest version is a commit count that a rebuild can lower, and Lance names version files u64::MAX - version so a listing resolves newest first; a stale higher-versioned manifest left in the bucket therefore wins over the one just published. The MinIO in a live test environment had accumulated three. Claude-Session: https://claude.ai/code/session_01J55jh5bX3t1DvTqbWx1qEN
75e20b8 to
2c329df
Compare
|
Thanks — that log made it clear, and the guest login works, so I can read TeamCity myself from now on. The cause is narrower than the digit drift I guessed at. Cosine distance is That is why the framework's 1e-6 relative tolerance did not absorb it: the rows below it passed, but relative error is meaningless around zero, and Two things confirm the diagnosis:
FixThe Float16 table is the one table in this suite whose data has no closed-form expected distance — Float64 records the L2 ladder The magnitude is still worth pinning — a backend that widened Float16 to Float32 before comparing, or lost the query vector's precision, would move it off one ulp — so it is now an assertion on the absolute value with a tolerance, rather than a literal whose sign a golden cannot avoid recording: assertTrue(Math.abs(f16SelfDistance) < 1e-5, ...)I also checked the rest of Full |
|
run buildall |
get it. let's test again in TeamCity. @FANNG1 |
…y matrix (#4082) Documents the Lance vector index compatibility matrix requested in [apache/doris#66495](apache/doris#66495), whose completion criteria include "User documentation contains a supported algorithm compatibility matrix". Follows up on the [review comment on apache/doris#66512](apache/doris#66512 (comment)) asking for the integration capability matrix to be added to this page. Companion code PR: [apache/doris#67039](apache/doris#67039), which adds the regression coverage for the combinations documented here. Both `docs/` and the `zh-CN` translation are updated together. ## What is added **A vector element type x distance metric matrix** under "Supported Vector Index Types", describing which combinations can serve an index-backed search with the embedded Lance version, with footnotes for: - the Float16 L2 index-build limitation (and that Flat Search over Float16 is fine); - UInt8 being hamming-only, and available only for `IVF_FLAT` and `IVF_HNSW_FLAT`; - Int8 having no complete index build path, plus the nullable-Int8 crash and the upstream fix it is waiting on. **A "The Query Metric Must Match the Index Metric" section**, because it is the most likely user-visible surprise: Doris selects a vector index only when the requested `metric` equals the metric the index was built with, and otherwise runs Flat Search. `EXPLAIN` reports this as `lanceSearchIndexSegments=0`. It also notes that only one index per vector column is considered. ## Two corrections to existing text 1. **Version binding.** The page said the BE data reader is bound to Lance `9.1.0-beta.3` at commit `e934cc2c`. That is the `lance-java` version the **FE** uses (`fe/pom.xml`). The BE reader is `lance-c v0.1.6`, which pins the Lance Rust crates at **`7.0.0-beta.7`**, Lance commit **`e0e977a6`**. Both are now stated separately. The `data_storage_version` table below it stays correct: `rust/lance-encoding/src/version.rs` at `e0e977a6` has default `V2_1`, `Stable -> V2_1`, `Next -> V2_3`, and `2.2` stable but not default, which is what the table already describes. 2. **Default `metric`.** The `metric` parameter row and the paragraph after the parameter table both said that omitting `metric` makes Doris use the metric the index was created with. `LanceScanNode.metricMatches` maps an unset metric to `l2`, so on a cosine-indexed table an omitted `metric` plans a Flat Search instead. The documented behavior now matches the code, and says to pass `metric` explicitly for a non-L2 index.
|
PR approved by anyone and no changes requested. |
|
PR approved by at least one committer and no changes requested. |
What problem does this PR solve?
Issue Number: Part of #66495
Problem Summary:
Companion documentation PR: apache/doris-website#4082.
#66512 gave
vector_search()end-to-end coverage for all six Lance ANN algorithms, but onlywith Float32 + L2. Reviewer feedback there was to merge it and follow up with the other
element types, which is what this PR does.
The gap is not only a dependency-compatibility one. #66841 made the FE metric-aware: Doris
plans an indexed split only when the requested metric equals the metric the index was built
with (
LanceScanNode.metricMatches), and otherwise plans flat splits rather than lettingLance fall back to brute force silently. Until now the cosine and dot branches of that
comparison had never run against a real index, because no non-L2 index existed anywhere in
the fixture. This PR covers them, in both directions.
What is changed?
There is no FE or BE production-code change.
The matrix is now covered in full, in two tiers
The support matrix has 96 cells (4 element types x 4 metrics x 6 algorithms). All 96 were
built against the embedded Lance generation, one per subprocess: 56 build and answer a
search, 40 do not, and the 40 fall into three groups — a float type with
hamming(IVFtraining rejects it),
uint8with a non-hamming metric (it is read as a binary vector), anduint8under a quantizing builder.buildable_combos()in the generator yields exactlythose 56; the measurement and the three failure groups are recorded next to it.
All 56 are covered:
Float64, Float16 and UInt8. Committed goldens, a closed-form distance ladder where the
data shape has one, and a discriminator per table. Data shape is per metric, because one
shape cannot serve them all: the collinear ladder is degenerate under cosine (directions
converge and the top distances collapse to zero) and under dot (the answer stops depending
on the query), so those tables use a directional shape, and UInt8 uses a thermometer code
that makes hamming an exact ladder.
doris.vs_index_matrix, at 64 rows with one vector column percell. One column per cell rather than several indexes on one column, because only the
first index built on a column is reachable — Lance answers the others with a brute-force
scan, and Doris arrives at the same place by another route, since
selectIndexSegmentskeeps only the segments of the first index it finds for a field id.
vs_index_matrixasserts three things per cell, and no goldens, so a fixture rebuild leavesit untouched:
lanceSearchUnindexedFragments=0— the only check thatwould catch an index reaching one of the two fragments.
nprobes=1answers differently fromnprobes=4. A flat scan has no partitions and cannot,so this is what separates a real indexed search from a silent fallback. Four query rows are
tried and the first that discriminates is enough.
refine_factor, the indexed search returns exactly the rows an exhaustivescan returns.
Recall is not measured, and (3) compares two paths inside the same backend, so it does not pin
absolute distances — that is what the 12 depth tables' closed-form ladders do.
Fixture self-check
The self-check is the whole contract for a fixture whose bytes are not reproducible, so it now
also pins a digest of every profile's vectors and reads rows back from each table; asserts the
metric each index was actually built with, read from
stats["indices"][*]["metric_type"]rather than searched for, because a cosine IVF_PQ reports
l2on its sub-index; keeps theindexed-vs-flat comparison bit-exact for the integer ladders; and enforces the
pylanceandlance-namespacepins before building or verifying. Discriminator margins are reported andwarned on when thin.
--repindowngrades a stale discriminator row to a warning so a rebuildcan complete and the row can then be re-measured against the fixture that was promoted.
Fixture publishing
iceberg.yaml.tplnow usesmc mirror --overwrite --removeinstead ofmc cp -r. The__manifestversion is a commit count that a rebuild can lower, and Lance names version filesu64::MAX - versionso a listing resolves newest first; a stale higher-versioned manifest leftin the bucket therefore wins over the one just published. A live test environment's MinIO had
accumulated several.
Also removes two
entrypoint.shfiles that agit add -Ahad picked up — they are renderedfrom the
.tplbeside them byrun-thirdparties-docker.sh— and adds the.gitignorerulethat was missing for them.
Release note
None
Check List (For Author)
Test
Behavior changed:
Does this need documentation?
[doc](lance) Document the vector element type and metric compatibility matrix doris-website#4082
Check List (For Reviewer who merge this PR)