- A float16 (half-precision) value is stored in a
short. It has 5 exponent bits; when all 5 are set the value is +Infinity, -Infinity or NaN. The largest finite half is 65504. - Lucene's float16 kNN field and queries reject a vector with such a component (
VectorUtil.checkFiniteFloat16), as the float32 field and query have since 9.7.0 (VectorUtil.checkFinite). - For exact scoring with a function query,
ConstKnnFloat16ValueSourcesupplies the query vector for every document andFloat16KnnVectorFieldSourceeach document's own vector;Float16VectorSimilarityFunctioncompares the two inside aFunctionQuery.ConstKnnFloatValueSourceis the float32 twin.
- Turn a float32 query into float16 with
Float.floatToFloat16. The largest finite half is 65504, so a component of 65520 becomes +Infinity (0x7C00), and NaN stays NaN (0x7E00). Pass it tonew ConstKnnFloat16ValueSource(q)to scoreKnnFloat16VectorFielddocuments with aFunctionQueryoverFloat16VectorSimilarityFunction. - You expect
IllegalArgumentExceptionright there:new ConstKnnFloatValueSource(new float[] {Float.NaN})throws it, and so doKnnFloat16VectorQueryandKnnFloat16VectorFieldfor the same float16 vector. - Instead the constructor returns normally, with assertions on and off. It only null-checks the array; the float32 twin wraps the same argument in
VectorUtil.checkFinite. - The bad value surfaces while scoring. Documents
[1, 2, 3],[-1, 2, 3],[0, 2, 3], query[+Inf, 1, 2]: with assertions on, EUCLIDEAN and DOT_PRODUCT end in anAssertionErrorinsideVectorUtilthat does not point at the constructor argument (no message for EUCLIDEAN;not finite: ... from <raw shorts>for DOT_PRODUCT; COSINE scores all three 0); with assertions off, EUCLIDEAN scores all three 0 and DOT_PRODUCT gives doc 0 a score of +Infinity (default boost 1).
Only applications on unreleased main (and branch_11x) that build ConstKnnFloat16ValueSource themselves; no Lucene code or query parser does, and its author called it a benchmark helper. A NaN or infinite component, such as a float32 value of 65520 or more cast to float16, then gives a late AssertionError or silent 0 or +Infinity scores. No index damage.
Mechanism, terms and numbers636 words
ConstKnnFloat16ValueSource (lucene/queries, added with float16 vectors in #16383) returns the same float16 query vector for every document; with Float16KnnVectorFieldSource and Float16VectorSimilarityFunction inside a FunctionQuery it scores documents by exact similarity to that vector. Its constructor only null-checks the short[] (ConstKnnFloat16ValueSource.java:31-33), while its float32 twin ConstKnnFloatValueSource wraps the argument in VectorUtil.checkFinite (ConstKnnFloatValueSource.java:33) and the float16 kNN query, similarity query and field call VectorUtil.checkFiniteFloat16. So a NaN or infinite half is accepted, with assertions on and off, and reaches VectorUtil at scoring time: with -ea, EUCLIDEAN, DOT_PRODUCT and MAXIMUM_INNER_PRODUCT fail an internal assert Float.isFinite(...) and COSINE silently scores 0; with -da, documents score 0 through FunctionQuery's intended NaN/negative clamp, or +Infinity under DOT_PRODUCT and MAXIMUM_INNER_PRODUCT with an infinite component (at the default boost of 1). Float.floatToFloat16 maps any float32 of magnitude 65520 or more to infinity, so one large component is enough. It is a missing fail-fast check and a parity gap with the twin, fixed by one line, on unreleased main only (also on branch_11x); no index damage, the match set is unchanged, and no Lucene code builds the class.
ConstKnnFloat16ValueSource(org.apache.lucene.queries.function.valuesource): "Function that returns a constant float16 vector value for every document." (ConstKnnFloat16ValueSource.java:27). Itsfloat16VectorVal(doc)returns the stored array for every document (:40-42).ConstKnnFloatValueSource: the float32 twin, in the same package since #12253 (9.7.0).VectorUtil.checkFiniteFloat16(VectorUtil.java:494-513): throwsIllegalArgumentExceptionwhen a half has all five exponent bits set,(v[i] & 0x7C00) == 0x7C00(:507); "@throws IllegalArgumentException if any component of the vector is not finite" (:503).
- The constructor is
this.vector = Objects.requireNonNull(constVector, "constVector");(ConstKnnFloat16ValueSource.java:31-33). Nothing checks the components. - The float32 twin is
this.vector = VectorUtil.checkFinite(Objects.requireNonNull(constVector, "constVector"));(ConstKnnFloatValueSource.java:32-34). The two files differ only in that line, theVectorUtilimport, the element type and the names. checkFiniteFloat16is called byKnnFloat16VectorQuery(:97),Float16VectorSimilarityQuery(:132) andKnnFloat16VectorField(:72,:97), not here.- At search time
FunctionQuery.AllScorervisits every document (DocIdSetIterator.all,FunctionQuery.java:115);Float16VectorSimilarityFunction.funcreturnssimilarityFunction.compare(v1, v2)with the document's vector and the constant (Float16VectorSimilarityFunction.java:46). - With
-ea,VectorUtil.squareDistance(short[], short[])(EUCLIDEAN) anddotProduct(short[], short[])(DOT_PRODUCT, MAXIMUM_INNER_PRODUCT) assert a finite result (VectorUtil.java:149,:84-91) and throw anAssertionError: with no message fromsquareDistance, withnot finite: <result> from <a>,<b>(the raw shorts) fromdotProduct; neither names the constructor argument.cosine(short[], short[])has no such assert (:117-123). - With
-da,FunctionQuery's score maps NaN and negative values to 0 (if (val >= 0 == false) ... return 0;,FunctionQuery.java:132-133), which is intended; a positive infinity passes the clamp and the score isboost * val(:135), so at the default boost of 1 it stays +Infinity.
Executed on main 645d4fe14a20: three documents with float16 vectors [1, 2, 3], [-1, 2, 3], [0, 2, 3]; query [h, 1, 2] with h each of the 5 non-finite halves 0x7C00 (+Infinity), 0xFC00 (-Infinity), 0x7E00, 0x7C01, 0xFFFF (NaN); default boost 1.
| similarity | assertions on (-ea) | assertions off (-da) |
|---|---|---|
| EUCLIDEAN | AssertionError (no message) at VectorUtil.squareDistance | all 3 documents score 0 |
| DOT_PRODUCT, MAXIMUM_INNER_PRODUCT | AssertionError: not finite: ... from <...> at VectorUtil.dotProduct | NaN: all 0; +Inf: doc 0 scores +Infinity, the others 0; -Inf: doc 1 scores +Infinity, the others 0 |
| COSINE | no assertion, all 3 score 0 | all 3 score 0 |
Float.floatToFloat16: 65504 and 65519 give 0x7BFF (65504), 65520 gives 0x7C00 (+Infinity), -100000 gives 0xFC00 (-Infinity), NaN gives 0x7E00 (OpenJDK 25.0.3). On OpenJDK 25 Lucene's vectorized code hands the float16 (short[]) kernels to the scalar implementation "until JDK 27" (PanamaVectorUtilSupport.java:310-325); the asserts sit in VectorUtil itself, above that choice.
Exposure and likelihood345 words
- Who is hit. Code on main that constructs
ConstKnnFloat16ValueSourcefrom a query vector with a NaN or infinite half. No Lucene code builds the class (it appears only in its own file) and no query parser does. In the #16383 review its author said it serves the benchmark's exact-scoring path and is "not needed in production path". - How a bad vector arises.
Float.floatToFloat16turns any float32 of magnitude 65520 or more into an infinity and keeps NaN, so a float32 query with one large component, or a NaN from an upstream normalization, is enough. - What happens. The constructor accepts it and the failure moves to scoring: with
-ea(Lucene's gradle test runs enable it for about 75% of seeds by default) anAssertionErrorfrom insideVectorUtilthat does not point at the constructor argument (no message for EUCLIDEAN;not finite: ... from <raw shorts>for DOT_PRODUCT and MAXIMUM_INNER_PRODUCT); with-da(production) silent 0 scores, or +Infinity for one document under DOT_PRODUCT or MAXIMUM_INNER_PRODUCT. The same vector gets a clearIllegalArgumentExceptionfromKnnFloat16VectorQuery. - What does not happen. No hang and no extra work (the per-document work is the same as for a finite vector), no change to which documents match (
FunctionQuerymatches every document), no index effect (the class is query-side only andKnnFloat16VectorFieldrejects non-finite halves at index time). - Default vs opt-in, scale. Default codec and searcher suffice, but the API is opt-in and rare: float16 vectors, the
@lucene.experimentalFunctionQueryand a non-finite query vector. Any index size; the repro needs no index at all. Likelihood: rare. - The same through
FunctionScoreQuery. The value source can also be scored withFunctionScoreQuerythroughValueSource.asDoubleValuesSource(), an adapter from 2017 (LUCENE-7610); #15329 (for #15328) made the vector-similarity value source implementdoubleVal, "allowing its use in FunctionScoreQuery".FunctionScoreQueryhas its own equivalent clamp: a missing, negative or NaN value scores 0 (FunctionScoreQuery.java:246-251). Executed withMatchAllDocsQueryas the inner query, the same three documents and the halves0x7C00,0xFC00,0x7E00: the outcomes match the table above, with-eaand-da.
One JUnit test, one statement: new ConstKnnFloat16ValueSource(new short[] {Float.floatToFloat16(Float.NaN)}) inside expectThrows(IllegalArgumentException.class, ...). No index, no randomness, single thread, about 1.5 s; the same result with assertions on and off.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.queries.function;
import org.apache.lucene.queries.function.valuesource.ConstKnnFloat16ValueSource;
import org.apache.lucene.tests.util.LuceneTestCase;
public class TestConstKnnFloat16ValueSourceNonFinite extends LuceneTestCase {
public void testNaNQueryVectorIsRejected() {
// expected IllegalArgumentException, as ConstKnnFloatValueSource throws for a NaN float; actual: accepted
expectThrows(
IllegalArgumentException.class,
() -> new ConstKnnFloat16ValueSource(new short[] {Float.floatToFloat16(Float.NaN)}));
}
}
cp TestConstKnnFloat16ValueSourceNonFinite.java $LUCENE/lucene/queries/src/test/org/apache/lucene/queries/function/
cd $LUCENE && ./gradlew :lucene:queries:test --tests org.apache.lucene.queries.function.TestConstKnnFloat16ValueSourceNonFinite -Ptests.seed=DEADBEEF -Ptests.asserts=true
# assertions off: -Ptests.asserts=false instead (same result)
- Expected
IllegalArgumentExceptionfrom the constructor, asConstKnnFloatValueSourcethrows for a NaN float.- Observed
Expected exception IllegalArgumentException but no exception was thrown: the NaN vector is accepted, with assertions on and off.
More files, environment, full output194 words
TestConstKnnFloat16ValueSourceNonFinite builds the value source from a 1-component vector holding the NaN half 0x7E00, the smallest input, and expects IllegalArgumentException, as ConstKnnFloatValueSource throws for a NaN float. Public API only; no index or directory; the seed does not matter. It compiles only where the float16 API exists (main from bfe5c847d3c). The scoring-time symptoms (the table above) follow from the accepted vector and are deliberately not asserted.
As the regression test: this same test, for example as a new method in TestKnnVectorSimilarityFunctions. With the fix it passes with assertions on and off. Its comment line is longer than 100 columns, which google-java-format flags; with the line wrapped the module's static checks pass.
| lucene | main 645d4fe14a20 (11.0.0-SNAPSHOT); no release has the class |
|---|---|
| jdk | OpenJDK 25.0.3 (build 25.0.3+9-LTS) |
| os | Linux, aarch64 |
cp TestConstKnnFloat16ValueSourceNonFinite.java $LUCENE/lucene/queries/src/test/org/apache/lucene/queries/function/
cd $LUCENE && ./gradlew :lucene:queries:test --tests org.apache.lucene.queries.function.TestConstKnnFloat16ValueSourceNonFinite -Ptests.seed=DEADBEEF -Ptests.asserts=true
# assertions off: -Ptests.asserts=false instead (same result)
TestConstKnnFloat16ValueSourceNonFinite > testNaNQueryVectorIsRejected FAILED
junit.framework.AssertionFailedError: Expected exception IllegalArgumentException but no exception was thrown
at __randomizedtesting.SeedInfo.seed([DEADBEEF:8D0C32D4C80B627D]:0)
at org.apache.lucene.tests.util.LuceneTestCaseParent.expectThrows(LuceneTestCaseParent.java:1432)
at org.apache.lucene.tests.util.LuceneTestCaseParent.expectThrows(LuceneTestCaseParent.java:1418)
at org.apache.lucene.queries.function.TestConstKnnFloat16ValueSourceNonFinite.testNaNQueryVectorIsRejected(TestConstKnnFloat16ValueSourceNonFinite.java:26)
# the same with -Ptests.asserts=false; with the fix: 1 test, 0 failures, assertions on and off- The float32 twin has rejected non-finite vectors since 9.7.0; a reviewer asked for that check "so no bad thing happen".
- CHANGES 9.7.0: "Require indexed KNN float vectors and query vectors to be finite." The float16 query and field enforce it.
- #16383 added the class "to be consistent with" the float32 one; nothing records a deliberate exception.
The full argument238 words
- The twin's check was requested in review. In #12253 uschindler wrote that
ConstKnnFloatVectorValueSource"should callVectorUtil.checkFinite(vector)in its constructor to make sure the float array is finite (like the Lucene KNNQuery and Field types), so no bad thing happen", and suggested exactly today's line. - The rule for the kNN APIs.
CHANGES.txt(9.7.0): "GITHUB#12281: Require indexed KNN float vectors and query vectors to be finite." It set this for Lucene's kNN field and query APIs; it is not stated as a contract for every value source. - The PR that added the class applied the rule to its siblings. In the #16383 review danmuzi wrote on
KnnFloat16VectorQuery: "The float32 implementation validates the target vector", and that a NaN fromcompare()"is only caught when assertions are enabled", proposingcheckFiniteFloat16; they asked for the same onKnnFloat16VectorField; both landed. On this class the author said it is "just there to be consistent with other classes ConstKnnFloatValueSource and ConstKnnByteValueSource", and msokolov replied "we may as well follow the existing pattern". Nobody discussed validation for this class. - No sign of intent. No test covers the class or any float16 function-query class; no comment, CHANGES entry or javadoc allows non-finite input here.
- A similar parity fix. #16708 (backport #16732) closed a similar validation-parity gap: columnar
addBatchskipped theKnn*VectorFieldchecks.CHANGES.txtlists it under Improvements (10.6.0), so it is precedent for this kind of hardening, not for calling it a bug.
The devil's-advocate check172 words
The strongest case for "not a bug" (the intended-behaviour devil's advocate and the cross-model "intended" review): no javadoc promises the exception (neither constructor has javadoc, and VectorSimilarityFunction.compare(short[], short[]) states no finiteness precondition); FunctionQuery is @lucene.experimental and clamps NaN to 0 on purpose; the class is a benchmark helper; and Lucene does not validate query vectors everywhere: DoubleValuesSource.similarityToQueryVector(..., short[], ...), FullPrecisionFloat16VectorSimilarityValuesSource and RescoreTopNQuery.createFullPrecisionRescorerQuery(short[]) accept a NaN query vector too (executed).
Why it fails: those three paths' float32 twins do not validate either, so they match their twins; of the four float32 classes that call VectorUtil.checkFinite (KnnFloatVectorField, KnnFloatVectorQuery, FloatVectorSimilarityQuery, ConstKnnFloatValueSource), only ConstKnnFloatValueSource has a float16 counterpart that does not validate. The twin's check was asked for in review, the class was added "to be consistent" with the twin, the same PR added checkFiniteFloat16 for exactly this input, and #12281 set the finite-vector rule for the kNN field and query APIs. Residual: a maintainer may call it hardening rather than a bug; not every other float16 entry point validates.
Recommended A. Validate in the constructor with checkFiniteFloat16, as the twin does
Wrap the argument in VectorUtil.checkFiniteFloat16, exactly as the float32 twin uses VectorUtil.checkFinite: one line plus the import. With it the repro passes with assertions on and off, all 112 org.apache.lucene.queries.function tests pass, and the module's static checks pass.
All options and trade-offs4 options ยท 437 words
Option A was validated through gradle in scratch worktrees of 645d4fe14a20; B, C and D were not run.
A. Validate in the constructor with checkFiniteFloat16, as the twin does (validated by the checks) (recommended)
this.vector = VectorUtil.checkFiniteFloat16(Objects.requireNonNull(constVector, "constVector")); A non-finite vector then fails at construction with IllegalArgumentException: non-finite float16 value at vector[0]=NaN (the helper's message format).
--- a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/ConstKnnFloat16ValueSource.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/ConstKnnFloat16ValueSource.java
@@ -23,13 +23,14 @@
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
+import org.apache.lucene.util.VectorUtil;
/** Function that returns a constant float16 vector value for every document. */
public class ConstKnnFloat16ValueSource extends ValueSource {
private final short[] vector;
public ConstKnnFloat16ValueSource(short[] constVector) {
- this.vector = Objects.requireNonNull(constVector, "constVector");
+ this.vector = VectorUtil.checkFiniteFloat16(Objects.requireNonNull(constVector, "constVector"));
}
@OverrideTrade-offs
- Parity with
ConstKnnFloatValueSource,KnnFloat16VectorQuery,Float16VectorSimilarityQueryandKnnFloat16VectorField; the error names the bad component. - Cost: one bitmask test per component, once per construction.
- Behaviour changes only for non-finite input, which no in-tree code passes.
- Like the twin, it checks once and keeps the caller's array, so later changes to that array are not caught.
- The changed line is exactly 100 columns; google-java-format accepts it.
What the checks validated
- Gradle, scratch worktree of
645d4fe14a20, seedDEADBEEF, 2026-11-01: without the fix the repro fails with assertions on and off; with it, it passes with assertions on and off. - With the fix,
./gradlew :lucene:queries:test --tests 'org.apache.lucene.queries.function.*': 15 classes, 112 tests, 0 failures, 0 skipped, includingTestKnnVectorSimilarityFunctions(11) and the repro. ./gradlew :lucene:queries:check -x testwith the fix and the repro as a new test file: google-java-format failed only on the repro's comment line, not on the fix, and the build stopped there, so ecj lint and forbidden APIs did not run in that run; with the line wrapped, the module's static checks pass (google-java-format, ecj lint, forbidden APIs; error-prone skipped). The full Lucene test suite was not run.- Earlier, with the fix shadowed into the stock jars: the finder's, the independent reproducer's and the cross-model reviewers' tests pass, on
ea57360912edand645d4fe14a20.
VectorUtil.checkFiniteFloat16(constVector.clone()) after the null check.
Trade-offs
Also guards against later changes to the caller's array (the copy question was raised in the #12253 review). Costs one allocation per construction and departs from the twin, which does not copy.
Check the vectors in Float16VectorSimilarityFunction.func.
Trade-offs
Catches any non-finite input, but costs a check per document, and document vectors are already validated at index time. Not recommended.
Say in the javadoc that non-finite components give unspecified scores.
Trade-offs
Keeps the asymmetry with the twin and with the float16 query and field. Not recommended.
No released version has the class; an application on main that passes user-supplied query vectors to it can reject non-finite components itself until a fix lands, as Lucene's own float16 kNN query does.
Non-formal: an automated comparison rewrote each float16 class into its float32 twin's spelling and diffed the pair; only this class drops the twin's finiteness check. A JUnit test on the stock jars then confirmed it with assertions on and off.
The method in full136 words
- Method: twin diff, the hunt's non-formal tier, in the hunt unit on float16 vectors. Every float16 class was rewritten into its float32 twin's spelling and diffed; the
ConstKnnFloat16ValueSourcepair differs only in the missingVectorUtilimport andcheckFinitecall. - Alongside: a bounded-exhaustive check over all 65,536 half bit patterns showed that
KnnFloat16VectorField,KnnFloat16VectorQueryandFloat16VectorSimilarityQueryreject exactly the non-finite halves; it did not cover this class, which is why the diff found it. - Witness: a 3-test JUnit on the stock main jar (float32-twin control, +Infinity, end-to-end EUCLIDEAN scoring) failed 2 of 3 with
-eaand-da; a shadowed one-line fix made it pass. - Later: an independent reproducer, an intended-behaviour devil's advocate, cross-model reviews and an adjudication checked it (2026-10-25); the test was reduced to one statement (2026-10-29) and re-verified on main
645d4fe14a20.
No prior report found in GitHub issues, PRs and review threads, commits, the JIRA archive, the mailing lists, downstream projects or the web (searched 2026-10-25 and 2026-10-26, re-checked 2026-11-01). The closest material is the #16383 review, which added the check to the float16 query and field but not here.
Searches4 searches
Closest items, none a report of this bug: #16383 (introduced the class; its review added the check to the float16 query and field, not here), #12253 (the twin's check, requested in review), #12281 (the finite-vector rule, 9.7.0), #16735 (Float16VectorSimilarityQuery, validates), #16725 (the open float16 tracker; it does not mention function-query value sources), #16708 / #16732 (a similar validation-parity gap, closed; CHANGES lists it as an Improvement). Gaps: a search shows only that no report was found; private channels were not searched.
| Where | When | How | Found |
|---|---|---|---|
| apache/lucene issues, PRs, commits and review threads | 2026-10-25, 2026-10-26 | five scripted prior-report searches (46 issue/PR and 8 commit queries); 101 manual read-only searches; the changed files of all 412 open PRs; the review threads of #16383, #12253, #12281, #16708, #14078, #15979 and #16795 read directly | no report; #16383's review is the closest material |
| Mailing lists, JIRA archive, downstream, web | 2026-10-25, 2026-10-26 | 68 list queries over dev@, java-user@, issues@ and commits@; 7 JIRA-archive queries; Elasticsearch, OpenSearch, Solr and luceneutil; 23 web queries | no report (list hits are notification mirrors of the related PRs) |
| Re-check: apache/lucene issues and PRs | 2026-11-01 | read-only searches sorted by creation date for ConstKnnFloat16ValueSource, checkFiniteFloat16, Float16VectorSimilarityFunction, "float16 NaN" and "fp16 non-finite" (issues; the first three also PRs) | nothing new: 0 issue hits; PR hits only #16383 and #16735, both already known |
| Upstream main and branches | 2026-11-01 | file contents at upstream main 2f424fd428da (2026-10-30) and branch_11x 603af870375 | not fixed; the file is unchanged since #16383 |
Introduced with float16 vectors by #16383 (bfe5c847d3c, merged 2026-07-27); the class never validated. No release has it: 10.6.0, 10.5.2 and 9.12.3 lack the float16 API. branch_11x carries the identical file, so 11.0.0 would ship it if nothing changes.
Versions tested5 rows
Introduced by #16383 "Add fp16 (float16) vector encoding support - no quantization support" (GitHub Pulkitg64; approved by danmuzi, jeho-rpls and msokolov), merge commit bfe5c847d3c on main, 2026-07-27. git log on the file returns only this commit. Not a regression: the class never validated, while its float32 twin has validated since 14c18d86245 (#12253, 2023-06-14, first in 9.7.0). The same commit added VectorUtil.checkFiniteFloat16 and used it in the float16 query and field; #16735 (2026-09-29) used it in Float16VectorSimilarityQuery.
| Version or commit | Verdict | Result |
|---|---|---|
main 645d4fe14a20 | affected | minimal repro fails with assertions on and off (standalone and gradle); passes with fix A |
main ea57360912ed, bfe5c847d3c (#16383) | introducing commit | the finder's repro fails 2 of 3 with -ea and -da; passes with the fix at bfe5c847d3c |
upstream main 2f424fd428da, branch_11x 603af870375 | read | identical file (blob 4ca6dd9d5799), not executed |
10.5.2, 10.5.0, 9.12.3, parent 606a26dafd55 | not affected | no float16 API: the repro does not compile; the float32 twin rejects non-finite vectors |
10.6.0, branch_10_6, branch_10x | read | no class; 10.6.0 and branch_10_6 have no float16 API (no KnnFloat16VectorField, no FLOAT16 encoding; 10.6.0 does not contain bfe5c847d3c) |
Confirmed with high confidence by an independent reproducer, an intended-behaviour devil's advocate, reviews by a reviewer from another model family (OpenAI GPT-5.6) and an adjudication; none dissented. Re-verified on main 645d4fe14a20; this doc's gradle run validated the fix.
Checks and verdicts301 words
Seven checks; all agree on the root cause. The reviews narrowed the claim: the 0 score is FunctionQuery's intended clamp, COSINE gives no AssertionError, and other float16 query-vector paths that skip validation match their float32 twins.
| Check | Verdict | What it found |
|---|---|---|
| cross-model review, refute lens (OpenAI GPT-5.6) | real, high confidence | Its own test fails under -ea and -da with -Infinity and NaN, a finite control passes, and the fix passes it and TestKnnVectorSimilarityFunctions (11/0). It corrected the claim: the 0 score is the intended clamp. |
| independent reproducer | real | Its own 5-test repro fails 2 of 5 under -ea and -da: all 5 non-finite halves are accepted while the twin and the float16 siblings reject them; it mapped the outcome per similarity; its own one-line fix passes 5/5. |
| cross-model review, intended lens (OpenAI GPT-5.6) | real, high confidence | Rated the intended-behaviour case moderate (experimental FunctionQuery, no @throws) and rejected it because of the validating twin, the float16 siblings and the #16383 history; its tests were re-run locally with new seeds. |
| intended-behaviour devil's advocate | real, low severity | No intended-behaviour argument survives; found the other unvalidated float16 query-vector paths and showed that their float32 twins do not validate either. |
| adjudication | confirmed, high confidence | Re-ran the reproducer's test with a new seed (2 of 5 fail under -ea and -da, 5/5 with the fix) and framed it as a missing fail-fast check, not a scoring bug. |
| main refreshes | still reproduces | Re-run on main a9779ef3c193 and 645d4fe14a20 with the same failures; reduced to a one-statement test on 2026-10-29. |
| brief validation (gradle) | fix A validated | Repro fails on stock main with assertions on and off and passes with fix A; the org.apache.lucene.queries.function tests and the module's static checks pass. |
The diagram as a still image, generated with AI and checked against the code. It is under 10 MB, the limit for a GitHub attachment.
- PNG 439 KB
Finding record, Lucene and upstream sources, repro files and diagram notes1,031 words
- Registry title
- ConstKnnFloat16ValueSource accepts Inf/NaN float16 query vectors that its float32 twin ConstKnnFloatValueSource rejects (missing VectorUtil.checkFiniteFloat16)
- Area
- queries / function vector sources
- Priority
- Low, rank 1167: ConstKnnFloat16ValueSource accepts NaN/Inf query halves that its float32 twin rejects, a benchmark-helper class in unreleased code with no index damage.
- Impact class
- wrong error
- Impact note
- Only applications on unreleased main and branch_11x (11.0.0) that construct a ConstKnnFloat16ValueSource themselves from a float16 query vector with an infinite or NaN half (for example a float32 query component of magnitude >= 65520 converted with Float.floatToFloat16) and score it with a FunctionQuery over Float16VectorSimilarityFunction; default codec and searcher, any scale; no index damage, no Lucene code or query parser builds the class, and its author called it a benchmark exact-scoring helper.
- Summary
- You build a ConstKnnFloat16ValueSource from a float16 query vector that contains an infinity or NaN half and score it against a KnnFloat16VectorField with a FunctionQuery over Float16VectorSimilarityFunction. You expect the constructor to throw IllegalArgumentException, as its float32 twin ConstKnnFloatValueSource does (VectorUtil.checkFinite) and as KnnFloat16VectorQuery and KnnFloat16VectorField do for the same halves (VectorUtil.checkFiniteFloat16). Instead the constructor only null-checks the array, so with -ea scoring later fails with an AssertionError inside VectorUtil (no message for EUCLIDEAN; 'not finite: ... from <raw shorts>' for DOT_PRODUCT and MAXIMUM_INNER_PRODUCT; COSINE silently scores 0), and with -da documents score 0 through FunctionQuery's intended NaN/negative clamp or, for DOT_PRODUCT and MAXIMUM_INNER_PRODUCT with an infinite component, +Infinity.
- Introduced
- bfe5c847d3c (#16383 'Add fp16 (float16) vector encoding') [read: git log, --diff-filter=A and git blame on ConstKnnFloat16ValueSource.java return only this commit, and the file is byte-identical at ea57360912ed; exec: the finder repro fails at bfe5c847d3c under -ea and -da and passes there with the one-line fix, and its parent 606a26dafd55 has no FLOAT16 API]; not a regression: the class has never validated, while its float32 twin has validated since 14c18d86245 (#12253, 2023-06-14) (2026-07-27)
- First affected version
- unreleased (main and the 11.0 release branch branch_11x, 11.0.0-SNAPSHOT; no release tag contains bfe5c847d3c)
- Affected versions
- main from bfe5c847d3c (2026-07-27) through 645d4fe14a2 [exec: both fail under -ea and -da at ea57360912ed and 645d4fe14a2 (run 2026-10-29)]; branch_11x 603af870375 has the identical file [read 2026-11-01]; 10.5.2, 10.5.0, 9.12.3 and the parent 606a26dafd55 not affected (no FLOAT16 API, the repro does not compile) [exec]; the float32 twin rejects non-finite vectors on 9.12.3, 10.5.0, 10.5.2, 606a26dafd55, bfe5c847d3c and main [exec control]; branch_10x 657014b3c57 and branch_10_6 4ae946e5a9b do not have the class [read 2026-10-29]; not fixed upstream as of main 645d4fe14a2 [exec 2026-10-29]
- Status
- documented (brief, checks and a per-finding doc exist)
- Verification
- Confirmed by AI-run checks: an independent repro, a cross-model review that agreed, an intended-behaviour check, a prior-report search and a skeptical audit; not yet triaged by a person.
- Found by
- non-formal
- Upstream and prior reports
- none found (2026-10-25 and 2026-10-26: 5 scripted prior-report searches with 46 GitHub issue/PR, 8 commit, 6 downstream, 68 mailing-list (dev@, java-user@, issues@, commits@), 7 JIRA-archive and 23 web queries; 101 manual read-only GitHub searches; changed-file lists of all 412 open PRs; review threads of #16383, #12253, #12281, #16708, #14078, #15979 and #16795 read directly); related prior items: #16383 (introduced the class; its review added checkFiniteFloat16 to the fp16 query and field because 'The float32 implementation validates the target vector', and separately kept this class to 'follow the existing pattern', but never discussed validation here), #12253 (the float32 twin's checkFinite, requested in review), #12281 (finite query and indexed vectors, CHANGES 9.7.0), #16735 (Float16VectorSimilarityQuery, validates), #16725 (open fp16 completion tracker, does not mention it), #16708 and its backport #16732 (a similar validation-parity gap, closed; CHANGES lists it under Improvements); no open PR touches the file
- Lucene commit tested
- main
645d4fe14a20: the minimal repro with assertions on and off, standalone and through gradle; the recommended fix in scratch worktrees with theorg.apache.lucene.queries.functiontests (15 classes, 112 tests) and:lucene:queries:check -x test; the independent reproducer's end-to-end test (every similarity, 5 non-finite halves). Mainea57360912edand the introducing commitbfe5c847d3c: the finder's repro. 10.5.2, 10.5.0, 9.12.3 and the parent606a26dafd55: no float16 API (the repro does not compile; the float32 twin rejects non-finite vectors). 10.6.0,branch_10_6andbranch_10xdo not have the class; upstream main2f424fd428daandbranch_11x603af870375have the identical file (read).
- Finding record
lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/ConstKnnFloat16ValueSource.java:27-33 (class javadoc 'returns a constant float16 vector value for every document'; constructor stores Objects.requireNonNull(constVector, "constVector") with no finiteness check)lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/ConstKnnFloatValueSource.java:32-34 (float32 twin: this.vector = VectorUtil.checkFinite(Objects.requireNonNull(constVector, "constVector")))lucene/core/src/java/org/apache/lucene/util/VectorUtil.java:495-513 (checkFiniteFloat16, '@throws IllegalArgumentException if any component of the vector is not finite'), :79-91 and :144-151 (dotProduct and squareDistance on short[] assert a finite result), :118-123 (cosine on short[] has no such assert)lucene/core/src/java/org/apache/lucene/search/KnnFloat16VectorQuery.java:97, lucene/core/src/java/org/apache/lucene/search/Float16VectorSimilarityQuery.java:132, lucene/core/src/java/org/apache/lucene/document/KnnFloat16VectorField.java:72, 97 (checkFiniteFloat16 on query targets and field values)lucene/queries/src/java/org/apache/lucene/queries/function/FunctionQuery.java:38 (@lucene.experimental), :115 (AllScorer iterates DocIdSetIterator.all), :129-136 (score maps NaN and negative values to 0)lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/Float16VectorSimilarityFunction.java:40-47 (func calls similarityFunction.compare on the constant and the document vector)lucene/CHANGES.txt:3510 ('GITHUB#12281: Require indexed KNN float vectors and query vectors to be finite.')
- The finding in 30 seconds
lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/ConstKnnFloat16ValueSource.java:27, 31-33, 40-42lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/ConstKnnFloatValueSource.java:32-34lucene/core/src/java/org/apache/lucene/util/VectorUtil.java:79-93, 117-123, 144-151, 494-513lucene/core/src/java/org/apache/lucene/search/KnnFloat16VectorQuery.java:97; Float16VectorSimilarityQuery.java:132; lucene/core/src/java/org/apache/lucene/document/KnnFloat16VectorField.java:72, 97lucene/queries/src/java/org/apache/lucene/queries/function/FunctionQuery.java:115, 129-137 (boost * val at :135); lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/Float16VectorSimilarityFunction.java:36-47lucene/core/src/java25/org/apache/lucene/internal/vectorization/PanamaVectorUtilSupport.java:59, 310-325 (float16 kernels delegated to the scalar code until JDK 27)
- Who is affected
lucene/queries/src/java/org/apache/lucene/queries/function/FunctionQuery.java:38 (@lucene.experimental), :115grep -rn ConstKnnFloat16ValueSource lucene --include='*.java' (only its own file)build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/TestsAndRandomizationPlugin.java:240-248 (tests.asserts default: ~75% of seeds)
- Why it looks unintended
lucene/CHANGES.txt:3543; :529, 537-539 (#16708 under Improvements, 10.6.0 section)git grep -n 'checkFinite(' at 645d4fe14a20 in lucene/*/src/java (four float32 callers); lucene/core/src/java/org/apache/lucene/document/column/ColumnValidation.java:476 (float16 column check)
- Suggested fix
lucene/core/src/java/org/apache/lucene/util/VectorUtil.java:503-511 (message format)
- Prior reports
a Lucene clone: git rev-parse origin/main:<path>, origin/branch_11x:<path> (blob 4ca6dd9d5799)
- History
a Lucene clone (2026-11-01): git rev-parse <ref>:<path> of ConstKnnFloat16ValueSource.java and KnnFloat16VectorField.java, git grep FLOAT16 VectorEncoding.java, for origin/main, origin/branch_11x, origin/branch_10_6, origin/branch_10x, releases/lucene/10.6.0; git merge-base --is-ancestor bfe5c847d3c releases/lucene/10.6.0 (false), origin/branch_11x (true)
- TestConstKnnFloat16ValueSourceNonFinite.java (JUnit, one test):
repro/LFV-092/src/org/apache/lucene/queries/function/TestConstKnnFloat16ValueSourceNonFinite.java(30 lines)
Made in parallel with this doc from the mechanism in the brief; the PNG is the summary frame.
assets/LFV-092/bug.png(439 KB)
