SEPURI-SAI-KRISHNA opened a new pull request, #57746:
URL: https://github.com/apache/spark/pull/57746

   ### What changes were proposed in this pull request?
   
   `VectorFunctionImplUtils` accumulates dot products and sums of squares in 
single precision. Those
   intermediate quantities are quadratic in the input values, so they overflow 
to infinity (or
   underflow to zero) long before the final result leaves the float range, and 
the function returns
   `NaN`, `Infinity`, `NULL` or an all-zero vector for inputs whose true result 
is perfectly
   representable as a float.
   
   This PR accumulates the intermediate dot products, sums of squares and 
absolute sums in `double`
   and rounds only the final result back to `float`. `vector_normalize` also 
keeps the norm in
   `double` instead of rounding it to a float before dividing, so a vector 
whose norm is outside the
   float range is still normalized correctly.
   
   Affected functions: `vector_cosine_similarity`, `vector_inner_product`, 
`vector_l2_distance`,
   `vector_norm`, `vector_normalize`.
   
   ### Why are the changes needed?
   
   Cosine similarity and normalization are scale invariant, so rescaling the 
input must not change
   the result -- but today it does:
   
   | Query | Before | After (correct) |
   | --- | --- | --- |
   | `vector_cosine_similarity(array(3.0e19F, 4.0e19F), array(3.0e19F, 
4.0e19F))` | `NaN` | `1.0` |
   | `vector_cosine_similarity(array(1.0e-23F, 0.0F), array(1.0e-23F, 0.0F))` | 
`NULL` | `1.0` |
   | `vector_l2_distance(array(3.0e19F, 4.0e19F), array(0.0F, 0.0F))` | 
`Infinity` | `5.0E19` |
   | `vector_norm(array(3.0e19F, 4.0e19F), 2.0F)` | `Infinity` | `5.0E19` |
   | `vector_normalize(array(3.0e19F, 4.0e19F), 2.0F)` | `[0.0, 0.0]` | `[0.6, 
0.8]` |
   | `vector_normalize(array(1.0e-23F, 0.0F), 2.0F)` | `NULL` | `[1.0, 0.0]` |
   
   In every row above the correct result is an ordinary float; only the 
intermediate sum overflows
   (`(3e19)^2 + (4e19)^2 = 2.5e39 > Float.MAX_VALUE`) or underflows (`(1e-23)^2 
= 1e-46 < the
   smallest positive float`). The wrong values are returned silently, so they 
propagate into
   similarity search results and rankings rather than failing loudly.
   
   Double-precision accumulation is also what other vector similarity 
implementations do
   (for example pgvector), and it removes a rounding error in the common case 
as well: the existing
   `vector_cosine_similarity(array(1.0F, 2.0F, 3.0F), array(4.0F, 5.0F, 6.0F))` 
result changes from
   `0.9746319` to `0.97463185`, which is the correctly rounded float of `32 / 
sqrt(1078)`
   (`0.9746318461970763...`).
   
   These functions were added in 4.2.0 by SPARK-54713 and SPARK-55030, so the 
wrong results are
   present in the 4.2.0 release.
   
   Documented behaviours are unchanged: empty vectors, `NULL` inputs, vectors 
containing `NULL`
   elements, and genuinely zero-magnitude vectors return exactly what they 
returned before.
   
   ### Does this PR introduce _any_ user-facing change?
   
   Yes. The functions listed above now return correct results instead of `NaN`, 
`Infinity`, `NULL`
   or zeros when an intermediate sum overflows or underflows the float range, 
as shown in the table
   above. This is a change relative to 4.2.0, where these functions were 
introduced.
   
   `vector_cosine_similarity` also changes by one ULP for ordinary inputs, 
because the result is now
   correctly rounded (`0.9746319` -> `0.97463185` for the example above).
   
   ### How was this patch tested?
   
   - New golden test cases in 
`sql/core/src/test/resources/sql-tests/inputs/vector-distance.sql` and
     `vector-norm.sql` covering large and small magnitude vectors for all five 
functions; golden
     files regenerated with `SPARK_GENERATE_GOLDEN_FILES=1`.
   - Existing suites: `SQLQueryTestSuite` (vector files), `MiscFunctionsSuite`, 
`ExpressionInfoSuite`
     (the `vector_cosine_similarity` example in `@ExpressionDescription` is 
updated to the correctly
     rounded value).
   
   On performance: the unrolled loops read through `ArrayData.getFloat()` / 
`isNullAt()`, and those
   accesses dominate the loop, so widening the accumulators is not measurable 
end to end. Timing the
   `vector_cosine_similarity` loop over 1536-dimensional `UnsafeArrayData` 
inputs (200k calls, JIT
   warmed up) gives 5834 ns/call before and 5891 ns/call after in one run, and 
5870 vs 5792 ns/call in
   the next -- i.e. within run-to-run noise. The arithmetic in isolation (plain 
`float[]`, no
   `ArrayData` indirection) is ~1.3x slower, but it accounts for only ~20% of 
the loop cost.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code (Opus 5)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to