Files
faiss/tests
ThatikondV 317a207bca ARM SVE optimization for L2 distance kernels adapted to new simd_impl structure (#5098)
Summary:
This PR introduces ARM SVE (Scalable Vector Extension) optimization for L2 metric functions. The new implementation extends the existing NEON/SVE SIMD logic with SVE vectorized kernels for distance computations, improving both indexing and search performance.

**Benchmarking Setup:**

To evaluate the performance impact of the new SVE-enabled L2 distance computations, a dedicated benchmarking script was developed. This script systematically compares the earlier FAISS version and the latest SVE-optimized version using the SIFT1M dataset with 128-dimensional vectors. The benchmarks cover all major index types like FLAT, IVF_FLAT, IVF_PQ, IVF_SQ8, and HNSW.

**Results:**
Index | OSS QPS | SVE QPS | Improvement
-- | -- | -- | --
Flat   (L2) | 888.53 | 1188.12 | 33.70%
IVF1024-Flat | 33,437.76 | 45,871.00 | 37.20%
IVF1024-PQ32x8 | 33,597.01 | 40,650.00 | 21.00%
HNSW   (M=16, ef=64) | 72,837.67 | 77,482.00 | 6.40%
IVF1024-SQ8 | 17,002.79 | 20,657.00 | 21.50%

The above results clearly demonstrate that enabling SVE vectorization yields consistent performance gains across all tested index types, where both indexing and search times improved significantly.

These improvements validate the effectiveness of the SVE implementation in accelerating L2 distance computations on modern ARM platforms.

**Co-author:** This work was jointly developed with abhijain1204fujitsu.

Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5098

Test Plan:
Run on an aarch64 host with SVE2, `svcntw() == 4` (128-bit vectors).

**The SVE code only exists in dynamic-dispatch builds.** In the default static aarch64 Buck build, `xplat.bzl` filters `utils/simd_impl/distances_arm_sve.cpp` out of `srcs` and `SINGLE_SIMD_LEVEL` resolves to `ARM_NEON`, so a static-mode run exercises nothing here. CI runs both modes.

## Correctness

```
buck2 test -c faiss.dynamic_dispatch=true \
  fbcode//faiss/tests:test_distances_simd \
  fbcode//faiss/tests:test_distances_dispatch \
  fbcode//faiss/tests:test_pq_encoding \
  fbcode//faiss/tests:test_ivfpq_indexing \
  fbcode//faiss/tests:test_ivfpq_codec \
  fbcode//faiss/tests:test_pq_code_distance \
  fbcode//faiss/tests:test_disable_pq_sdc_tables
# Pass 32. Fail 0.

buck2 test -c faiss.dynamic_dispatch=true \
  fbcode//faiss/tests:test_build_blocks fbcode//faiss/tests:test_product_quantizer
# Pass 105. Fail 0.

buck2 test -c faiss.dynamic_dispatch=true fbcode//faiss/tests:test_index   # Pass
buck2 test fbcode//faiss/tests:test_distances_simd fbcode//faiss/tests:test_distances_dispatch
# static mode: Pass 9. Skip 6 (only one SIMD level available).
```

`buck2 build fbcode//mode/opt fbcode//faiss:faiss -c faiss.dynamic_dispatch=true` and the static build both clean. `arc f` / `arc lint` clean.

`test_distances_dispatch` forces every available level and compares against `SIMDLevel::NONE`; in DD mode on this host it reports `NONE`/`ARM_NEON`/`ARM_SVE` and no longer skips, so the `ARM_SVE` specializations are genuinely under test.

Added coverage in `tests/test_distances_simd.cpp`:
- `TestFvecL2sqrNy.D2`: `dim` swept over `{1,2,3,4,5,8,12,16,24,32,48,64}` (was `{2,4,8,12}`) so the `lanes1`..`lanes4` kernels are reached on 128/256/512-bit SVE, not just the `d1/d2/d4/d8` cases.
- Both `_y_transposed` tests: `ny` swept over `{5,8,11,16,32}` (was hardcoded `11`, which never produced a full-vector block on any SVE width, so the no-tail path was untested).

## Kernel microbenchmark

`mode/opt`, DD, pinned core, alternating runs, min-of-N x median-of-7, run-to-run spread <1%. `ny = 256` (ksub), `d = dsub`. vs this diff's parent:

| kernel | speedup range over parent |
|---|---|
| `fvec_L2sqr_ny` | +17% to +725% (dsub 3 and 6 hit the scalar fallback, unchanged) |
| `fvec_L2sqr_ny_nearest` | +48% to +130% |
| `fvec_L2sqr_ny_transposed` | +234% to +298% |
| `fvec_L2sqr_ny_nearest_y_transposed` | +318% to +658% |

## Index-level

`faiss.contrib.datasets.SyntheticDataset`, d=128, nb=400k, nq=2k, k=1, single-threaded, min/best of 3 alternating rounds. "kernels" = SVE kernels with the pre-existing A0 dispatch; "wired" = this diff.

PQ encode, `add()` seconds:

| Index | parent | kernels only | wired | speedup |
|---|---|---|---|---|
| IVF1024-PQ32x8 | 16.17 | 16.16 | 6.32 | **2.56x** |
| PQ32x8 (flat) | 14.80 | 14.82 | 4.97 | **2.98x** |
| Flat / IVF-Flat / HNSW / IVF-SQ8 | - | - | - | 1.00x |

Search QPS is unchanged everywhere (-0.9% to +0.5%, inside 0.2-3.0% run-to-run variance). recall@1 is identical to the parent for every index.

The "kernels only" column is 1.00x because `ProductQuantizer` dispatched through `with_simd_level()` = `AVAILABLE_SIMD_LEVELS_A0`, which lacks the `ARM_SVE` bit, so the `case SIMDLevel::ARM_SVE:` arm failed its `if constexpr` and fell through to `ARM_NEON`. Verified by putting `abort()` in `fvec_L2sqr_ny_nearest<ARM_SVE>`: PQ encoding completed without hitting it, while `test_distances_dispatch` (A1) aborted immediately. The three `with_simd_level_a1` call sites in `impl/ProductQuantizer.cpp` are what close that gap.

`compute_inner_prod_table` still uses A0 and so cannot reach the pre-existing `fvec_inner_products_ny<ARM_SVE>`; left alone as it is outside this diff's L2 scope and unmeasured here.

Reviewed By: junjieqi

Differential Revision: D106149015

Pulled By: mnorris11

fbshipit-source-id: c1144fc8fd0eefb30c9dcc8084b472152623a88e
2026-08-25 10:20:33 -07:00
..
2026-05-05 16:54:09 -07:00
2026-05-01 09:55:55 -07:00