Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
63040e1
feat(lance-linalg): runtime SIMD dispatch for pre-Haswell x86_64 from…
tobocop2 Apr 28, 2026
6141fed
fix(lance-linalg): gate f32 cosine dispatch behind cfg(target_feature…
stumpylog Jun 10, 2026
378dbb0
Merge branch 'main' into fix/runtime-simd-multiversion
tobocop2 Jul 7, 2026
77cce31
Merge remote-tracking branch 'upstream/main' into fix/runtime-simd-mu…
tobocop2 Jul 8, 2026
d39482c
test(lance-linalg): cover batch cosine kernels and scalar x86 gather
tobocop2 Jul 8, 2026
8a08fa8
Merge branch 'main' into fix/runtime-simd-multiversion
tobocop2 Jul 9, 2026
d000a67
chore: address review feedback on runtime SIMD dispatch
tobocop2 Jul 9, 2026
2c71e4c
Merge branch 'main' into fix/runtime-simd-multiversion
tobocop2 Jul 9, 2026
d5205dc
refactor: share the AVX horizontal-sum helper across the distance ker…
tobocop2 Jul 9, 2026
bdeda97
test: parameterize the AVX horizontal-sum tests with rstest
tobocop2 Jul 9, 2026
1903ba4
fix: bounds-check the scalar gather fallback on x86_64
tobocop2 Jul 9, 2026
acf70b7
Merge remote-tracking branch 'upstream/main' into fix/runtime-simd-mu…
tobocop2 Jul 9, 2026
c247ddd
fix: validate gather indices once, before backend dispatch
tobocop2 Jul 9, 2026
fb2403c
Merge branch 'main' into fix/runtime-simd-multiversion
tobocop2 Jul 9, 2026
0969e3d
Merge branch 'main' into fix/runtime-simd-multiversion
tobocop2 Jul 9, 2026
bb8af46
fix: require FMA before selecting the AVX2 dispatch tier
tobocop2 Jul 9, 2026
2c16954
perf: choose the SIMD tier once per batch for f32 dot and l2
tobocop2 Jul 9, 2026
97865b5
perf: keep the batch iterator wrapper-free on AVX2-baseline builds
tobocop2 Jul 10, 2026
334229a
perf: keep the AVX+FMA kernel on the AVX2-baseline batch path
tobocop2 Jul 10, 2026
07cd967
fix: require avx2 and fma before taking the direct-kernel batch path
tobocop2 Jul 10, 2026
6b94ccd
perf: only use the AVX kernel where the scalar path degenerates
tobocop2 Jul 10, 2026
67586aa
Merge branch 'main' into fix/runtime-simd-multiversion
tobocop2 Jul 10, 2026
86e6c84
Merge branch 'main' into fix/runtime-simd-multiversion
tobocop2 Jul 10, 2026
225bd4b
Merge branch 'main' into fix/runtime-simd-multiversion
tobocop2 Jul 10, 2026
18d93d4
Merge branch 'main' into fix/runtime-simd-multiversion
tobocop2 Jul 13, 2026
b1670de
fix: gate f32 batch dispatch kernels to sub-avx2+fma builds
tobocop2 Jul 13, 2026
6d0f401
Merge branch 'main' into fix/runtime-simd-multiversion
tobocop2 Jul 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,42 @@ jobs:
- name: Check benchmarks
run: cargo check --profile ci --benches

qemu-pre-haswell:
# Verifies that lance-linalg's runtime SIMD dispatch still works
# correctly when the binary is built with the lower x86-64-v2 baseline
# (the legacy build path documented in CONTRIBUTING.md). Emulates a
# Nehalem CPU under qemu-user; catches any accidental AVX2/FMA
# instructions that leak past the runtime dispatch.
#
# The published-wheel default baseline (`target-cpu=haswell`) is set in
# `.cargo/config.toml`; this job overrides RUSTFLAGS for one job to
# exercise the legacy path without affecting any other build.
name: pre-Haswell SIGILL check (qemu Nehalem)
runs-on: ubuntu-24.04
timeout-minutes: 60
env:
CC: clang
CXX: clang++
RUSTFLAGS: "-C target-cpu=x86-64-v2"
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER: "qemu-x86_64 -cpu Nehalem"
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '250,320p' .github/workflows/rust.yml

Repository: lance-format/lance

Length of output: 3077


Disable persisted checkout credentials
.github/workflows/rust.yml:275actions/checkout leaves the repo token in git config by default, and this job runs cargo test, so build scripts or tests can read it. Set persist-credentials: false here.

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 275-275: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/rust.yml at line 275, The checkout step currently leaves
the repository token persisted in git config, which can be exposed during the
cargo test job. Update the actions/checkout usage in the rust workflow to set
persist-credentials to false so build scripts and tests cannot read the token;
keep the change scoped to the checkout step identified by actions/checkout.

Source: Linters/SAST tools

- name: Setup rust toolchain
run: |
rustup toolchain install stable
rustup default stable
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Rust cache is enabled by default (cache poisoning risk).

Swatinem/rust-cache caches by default; flagged by zizmor as a potential cache-poisoning vector depending on this workflow's trigger context.

Please confirm this job's triggers (e.g. whether it runs on pull_request from forks) to assess exploitability; if untrusted PRs can populate this cache, consider save-if: ${{ github.ref == 'refs/heads/main' }} or disabling cache for this job.

🧰 Tools
🪛 zizmor (1.26.1)

[error] 280-280: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/rust.yml at line 280, The Rust cache step in the workflow
may allow untrusted cache writes depending on the job triggers, so first verify
how this job runs (especially whether it executes on pull_request from forks).
In the workflow entry that uses Swatinem/rust-cache, either restrict cache
population with save-if so only trusted branches like refs/heads/main can save,
or disable caching for this job if untrusted PRs can reach it. Keep the fix
localized to the rust cache step and the workflow triggers that govern it.

- name: Install dependencies
run: |
sudo apt update
sudo apt install -y protobuf-compiler libssl-dev qemu-user
- name: Build lance-linalg lib tests in release mode
run: |
cargo test --release -p lance-linalg --lib --no-run
- name: Run lance-linalg lib tests under qemu Nehalem
run: |
cargo test --release -p lance-linalg --lib

msrv:
# Check the minimum supported Rust version
name: MSRV Check - Rust v${{ matrix.msrv }}
Expand Down
10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ Currently Lance is implemented in Rust and comes with a Python wrapper. So you'l
a. Install pre-commit: https://pre-commit.com/#install
b. Run `pre-commit install` in the root of the repo

## Building for legacy x86_64 hosts (pre-Haswell)

The default workspace build targets `haswell` (AVX2 + FMA + F16C), matching the published wheels. To build a binary that runs on pre-Haswell silicon (Sandy Bridge / Ivy Bridge / Westmere on Intel, Bulldozer / Piledriver / Steamroller on AMD — i.e. CPUs without AVX2), set the baseline yourself at build time:

```sh
RUSTFLAGS="-C target-cpu=x86-64-v2" cargo build --release
```

Runtime SIMD dispatch in `lance-linalg::distance` will then pick the appropriate tier (scalar / AVX / AVX+FMA / AVX2+FMA / AVX-512) based on the host. From Python, use `lance.simd_info()` to verify which tier was selected.

Comment on lines +28 to +37

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== CONTRIBUTING excerpt =="
sed -n '20,45p' CONTRIBUTING.md

echo
echo "== Search for target-cpu / haswell references =="
rg -n --hidden --glob '!**/target/**' --glob '!**/node_modules/**' 'target-cpu|x86-64-haswell|haswell' .

Repository: lance-format/lance

Length of output: 4511


Use target-cpu=haswell here. CONTRIBUTING.md:30 should match .cargo/config.toml; the workspace baseline is target-cpu=haswell, not x86-64-haswell.

Proposed fix
-The default workspace build targets `x86-64-haswell` (AVX2 + FMA + F16C), matching the published wheels. To build a binary that runs on pre-Haswell silicon (Sandy Bridge / Ivy Bridge / Westmere on Intel, Bulldozer / Piledriver / Steamroller on AMD — i.e. CPUs without AVX2), set the baseline yourself at build time:
+The default workspace build targets `haswell` (AVX2 + FMA + F16C), matching the published wheels. To build a binary that runs on pre-Haswell silicon (Sandy Bridge / Ivy Bridge / Westmere on Intel, Bulldozer / Piledriver / Steamroller on AMD — i.e. CPUs without AVX2), set the baseline yourself at build time:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## Building for legacy x86_64 hosts (pre-Haswell)
The default workspace build targets `x86-64-haswell` (AVX2 + FMA + F16C), matching the published wheels. To build a binary that runs on pre-Haswell silicon (Sandy Bridge / Ivy Bridge / Westmere on Intel, Bulldozer / Piledriver / Steamroller on AMD — i.e. CPUs without AVX2), set the baseline yourself at build time:
```sh
RUSTFLAGS="-C target-cpu=x86-64-v2" cargo build --release
```
Runtime SIMD dispatch in `lance-linalg::distance` will then pick the appropriate tier (scalar / AVX / AVX+FMA / AVX2+FMA / AVX-512) based on the host. From Python, use `lance.simd_info()` to verify which tier was selected.
## Building for legacy x86_64 hosts (pre-Haswell)
The default workspace build targets `haswell` (AVX2 + FMA + F16C), matching the published wheels. To build a binary that runs on pre-Haswell silicon (Sandy Bridge / Ivy Bridge / Westmere on Intel, Bulldozer / Piledriver / Steamroller on AMD — i.e. CPUs without AVX2), set the baseline yourself at build time:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CONTRIBUTING.md` around lines 28 - 37, The build instructions in
CONTRIBUTING.md use the wrong baseline target; update the example to match the
workspace setting from .cargo/config.toml by using target-cpu=haswell instead of
x86-64-v2. Keep the surrounding explanation and the SIMD dispatch note in sync
with the build example so readers see the same baseline used by the workspace.

## Sample Workflow

1. Fork the repo
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions python/python/lance/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
ScanStatistics,
bytes_read_counter,
iops_counter,
simd_info,
)
from .mem_wal import (
ExecutionPlan,
Expand Down Expand Up @@ -115,6 +116,7 @@
"json_to_schema",
"schema_to_json",
"set_logger",
"simd_info",
"write_dataset",
"FFILanceTableProvider",
"IndexProgress",
Expand Down
22 changes: 22 additions & 0 deletions python/python/tests/test_lance.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,28 @@ def test_io_counters(tmp_path):
assert lance.bytes_read_counter() > starting_bytes


def test_simd_info():
info = lance.simd_info()
assert info["tier"] in (
"none",
"sse",
"avx",
"avx_fma",
"avx2",
"avx512",
"avx512_fp16",
"neon",
"lsx",
"lasx",
)
assert isinstance(info["target_arch"], str) and info["target_arch"]
if info["target_arch"] == "x86_64":
# The x86_64 ABI mandates SSE2.
assert "sse2" in info["host_features"]
else:
assert info["host_features"] == []


@pytest.mark.parametrize(
"row_param, column_name",
[("with_row_id", "_rowid"), ("with_row_address", "_rowaddr")],
Expand Down
32 changes: 32 additions & 0 deletions python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ fn lance(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(language_model_home))?;
m.add_wrapped(wrap_pyfunction!(bytes_read_counter))?;
m.add_wrapped(wrap_pyfunction!(iops_counter))?;
m.add_wrapped(wrap_pyfunction!(simd_info))?;
m.add_wrapped(wrap_pyfunction!(stable_version))?;
// Debug functions
m.add_wrapped(wrap_pyfunction!(debug::format_schema))?;
Expand All @@ -347,6 +348,37 @@ fn iops_counter() -> PyResult<u64> {
Ok(::lance::io::iops_counter())
}

/// Returns a dict describing which SIMD tier the lance runtime dispatches to
/// on this host, plus the raw CPU feature flags it detected.
///
/// Mirrors `pyarrow.runtime_info()`: a cheap, transparent way to verify that
/// the host is hitting the expected SIMD tier (e.g., `"avx512_fp16"`,
/// `"avx2"`) when debugging vector-search performance.
///
/// Returns:
/// {
/// "tier": str, # e.g. "avx2", "avx_fma", "neon", "none"
/// "target_arch": str, # e.g. "x86_64", "aarch64", "loongarch64"
/// "host_features": list[str], # raw CPU feature flags (x86_64 only)
/// }
Comment on lines +354 to +363

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: I like this API.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you!

///
/// Examples:
/// >>> import lance
/// >>> info = lance.simd_info()
/// >>> sorted(info)
/// ['host_features', 'target_arch', 'tier']
/// >>> isinstance(info["tier"], str)
/// True
#[pyfunction]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
pub fn simd_info(py: Python<'_>) -> PyResult<Py<PyAny>> {
let info = lance_core::utils::cpu::simd_info();
let dict = pyo3::types::PyDict::new(py);
dict.set_item("tier", info.tier.to_string())?;
dict.set_item("target_arch", info.target_arch)?;
dict.set_item("host_features", info.host_features)?;
Ok(dict.into())
}

#[pyfunction(name = "bytes_read_counter")]
fn bytes_read_counter() -> PyResult<u64> {
Ok(::lance::io::bytes_read_counter())
Expand Down
Loading
Loading