Skip to content

feat: Add native fixed-array dot - #28504

Open
0guban0v wants to merge 12 commits into
pola-rs:mainfrom
0guban0v:feat/native-fixed-array-dot
Open

feat: Add native fixed-array dot#28504
0guban0v wants to merge 12 commits into
pola-rs:mainfrom
0guban0v:feat/native-fixed-array-dot

Conversation

@0guban0v

@0guban0v 0guban0v commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Closes #17456 and benefits embedding pipelines doing similarity scoring or candidate reranking.

Also, related to #24652 requesting vertical fixed-array aggregation Expr.sum() reducing Array rows into one Array.

Issue example uses Expr.dot, which reduces columns to one scalar. This PR adds arr.dot because requested operation is row-wise over each fixed-size array.

  df.select(
      score=pl.col("embedding").arr.dot(query)
  )

API distinction:

# Column reduction producing one scalar
pl.col("a").dot("b")

# Row-wise Array operation producing one scalar per row
pl.col("a").arr.dot("b")

Existing equivalent materializes a rows × width multiplication result before reducing:

(pl.col("a") * pl.col("b")).arr.sum()

Native implementation reads both arrays and writes one scalar per row. For 1M rows of Array(Float32, width=768), this avoids roughly 2.86 GiB multiplication buffer.
Attached benchmark in comments.

Scope:

  • matching Float32 or Float64 inner dtypes;
  • equal widths;
  • equal row counts or one-row broadcasting;
  • outer null propagation;
  • inner-null products ignored;
  • fragmented inputs rechunk through existing behavior;
  • raw Python sequences, one-dimensional NumPy arrays, and literal vectors are cast to the left Array dtype;
  • non-literal expressions and Series still require exactly matching Array dtypes;

Limitations:

  • do not support Integer or mixed-float

Update (2026-07-30): arr.dot now accepts Python sequences and one-dimensional NumPy arrays directly. These raw query vectors inherit complete Array dtype of the left expression. Explicit expressions and Series operands retain their dtype and must match the left operand.

@github-actions github-actions Bot added enhancement New feature or an improvement of an existing feature python Related to Python Polars rust Related to Rust Polars changes-dsl Do not merge if this label is present and red. labels Jul 24, 2026
@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.26190% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.08%. Comparing base (36e414b) to head (1d0be5d).

Files with missing lines Patch % Lines
crates/polars-ops/src/chunked_array/array/dot.rs 92.39% 7 Missing ⚠️
crates/polars-plan/src/dsl/array.rs 62.50% 3 Missing ⚠️
...polars-plan/src/plans/aexpr/function_expr/array.rs 89.47% 2 Missing ⚠️
crates/polars-plan/src/dsl/function_expr/array.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #28504      +/-   ##
==========================================
- Coverage   81.71%   79.08%   -2.64%     
==========================================
  Files        1870     1871       +1     
  Lines      262992   263158     +166     
  Branches     3217     3220       +3     
==========================================
- Hits       214917   208106    -6811     
- Misses      47243    54221    +6978     
+ Partials      832      831       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@0guban0v
0guban0v force-pushed the feat/native-fixed-array-dot branch from c9347ef to c28fe48 Compare July 24, 2026 13:40
@0guban0v

0guban0v commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Possible follow-up, separate from this PR, is to fuse sibling arr.dot expressions that score same Array column against several constant query vectors.

df.select(
    score_a=pl.col("embedding").arr.dot(query_a),
    score_b=pl.col("embedding").arr.dot(query_b),
)

Today each expression traverses embedding independently. Planner could read each embedding array once, compute both scores in one multi-query kernel, and still return two separately named columns. Expressions that cannot be fused would continue using existing arr.dot execution.

Standalone Apple M4 experiment found this faster from two query vectors onward, but it has not been validated inside Polars and floating-point tolerance is unresolved. I don't want to mess with baseline.

@0guban0v

Copy link
Copy Markdown
Contributor Author

Addressed raw-query-vector API mismatch. IntoExpr includes Python lists and NumPy arrays, but these previously became List or primitive Series literals and failed fixed-size Array requirement. Basically, bump data science product fit.

@0guban0v

0guban0v commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Not much difference between Float 32 and Float 64. I will check rechunk impact on memory in the follow-up work.

m4-product-canary-trace-f32 m4-product-canary-peak-rss

@0guban0v
0guban0v marked this pull request as ready for review July 30, 2026 19:44
0guban0v added 6 commits July 30, 2026 13:21
Allow Python lists, 1D NumPy arrays, and literal vectors to inherit the left Array dtype, matching embedding-scoring workflows without weakening strict dtype checks between columns.
Document null and floating-point behavior, and cover all-missing rows, special values, cancellation, fragmented inputs, and wide all-valid arrays.
@0guban0v
0guban0v force-pushed the feat/native-fixed-array-dot branch from d3df4a5 to 3e79306 Compare July 30, 2026 20:21
@0guban0v

Copy link
Copy Markdown
Contributor Author

@orlp , @ritchie46 , whenever you have time, this one is ready. I don't want to grow scope, it's reasonable with limitations and follow-up work. Happy to answer your questions.

@0guban0v

Copy link
Copy Markdown
Contributor Author

Verified current implementation explicitly on

  q.collect(engine="streaming")
  q.collect(engine="in-memory")

both produced identical results

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changes-dsl Do not merge if this label is present and red. enhancement New feature or an improvement of an existing feature python Related to Python Polars rust Related to Rust Polars

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cannot dot two Array(float64, N) columns

1 participant