Commit Graph
6 Commits
Author SHA1 Message Date
Michael Norris e2cf9e1ebf Accept ragged code sizes in faiss::hammings(), and fix the vectorization axis for wide codes (#5520)
Summary:
Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5520

Two changes to `faiss::hammings()`: it accepts ragged code sizes, and the kernel for wide codes is told which axis to vectorize.

Throughout, **ncodes** is number of bytes per vector (so, 32 for a 256-bit vector), a **word** is one 64-bit (8-byte) unit. A code size is **word-multiple** when `ncodes % 8 == 0` and **ragged** otherwise, so a code is `nwords = ncodes / 8` whole words followed by `rem = ncodes % 8` trailing bytes.

## 1. "Ragged" code sizes

Ragged here doesn't mean variable vector lengths, it means non-divisible by 8 ncodes.

`hammings()` throws unless the code size is word-multiple, even though the other Hamming entry points (`hammings_knn_hc`, `hamming_range_search`) have always accepted ragged sizes. Callers who need them have to reach past `hammings()` into `with_simd_level_a0_spr` and `with_HammingComputer` and hand-roll the sweep; Laser does exactly that in `KnnFaissBinaryIndex::measureAllCentroids` (D115840549) and can drop it after this.

Ragged sizes go to `hammings_ragged_dispatch()`, behind its own `hammings_ragged_fixSL<SL>` entry point so the word-multiple kernels keep the code they generate today.

## 2. Vectorization axis for wide codes

All-pairs Hamming is a doubly nested loop: over candidates, and over the words of one code. Only one of them gets vectorized, and clang picks the candidate loop. That means each vector lane wants word `k` of a different candidate, and since candidates are `ncodes` bytes apart the load becomes a strided gather:

```
  across candidates (today)           within one code (this diff)
  vpgatherqq (%r8,%zmm19,1),%zmm20    vpxorq (%rbx,%r12,1),%zmm4,%zmm5
```

Vectorizing the other loop instead reads one whole code contiguously, which is a plain vector load. `#pragma clang loop vectorize(disable)` on the candidate loop is what expresses that: it does not disable vectorization, it takes the candidate loop out of the running so the word loop is what gets vectorized. The arithmetic is identical either way.

It is applied only when `nwords >= 4`, i.e. once a code is at least a full 256-bit register wide. That is the widest threshold that costs nothing anywhere:

| threshold | ncodes 8 | ncodes 16 | ncodes 32 | ncodes 64 |
| --- | --- | --- | --- | --- |
| not applied | +0.0 / -0.1 | +0.0 / -0.6 | +0.1 / -0.2 | -0.1 / +0.2 |
| `nwords >= 1` | **+45.3 / +63.5** | -10.4 / **+12.2** | -12.6 / -22.7 | -40.7 / -63.1 |
| `nwords >= 2` | +0.1 / +0.0 | -10.5 / **+11.7** | -12.6 / -23.7 | -40.7 / -63.1 |
| `nwords >= 4` | +0.2 / +0.1 | -0.1 / +0.1 | -12.7 / -23.7 | -40.7 / -63.1 |

(AVX2 / AVX512, percent against the parent commit.) At one word the candidate loop is already contiguous, so taking it out of the running costs 45-63%. At two words it helps AVX2 at ncodes 16 and hurts AVX512 by the same amount, which would make the behaviour SIMD-level dependent. Four words is where both levels agree.

The pragma is clang-only and guarded with `#if defined(__clang__)`, since MSVC warns C4068 on an unknown pragma. Other compilers therefore never see it and keep today's behaviour: correct, just without the gain.

## 3. Why the ragged kernel is templated

A ragged code's loads must be `memcpy` because it is not 8-byte aligned. Both `nwords` and `rem` are template parameters, which is worth an order of magnitude:

| ncodes | `<NW, REM>` | `<NW>`, rem runtime | neither templated |
| --- | --- | --- | --- |
| 2 | **0.487** | 8.720 | 8.724 |
| 4 | **0.433** | 9.419 | 9.164 |
| 10 | **0.868** | 9.344 | 10.160 |
| 12 | **0.692** | 10.387 | 11.472 |
| 20 | **1.066** | 9.879 | 10.730 |
| 33 | **2.646** | 10.757 | 10.769 |
Fig 1: ragged table comparing templates NW and REM

(AVX2, ns per pair.) `rem` is the critical part: at a constant size `memcpy` folds into a single instruction, whereas at a runtime size it becomes a real `memcpy` call per candidate, costing 10-20x. That is why the dispatch switches on `ncodes % 8` rather than passing the remainder as an argument.

`NW` is worth a further ~2x, but only for 1 to 3 whole words, where a compile-time trip count lets the word loop unroll to straight-line popcounts:

| ncodes | NW templated | NW runtime |
| --- | --- | --- |
| 2, 4 (0 words) | 0.489 / 0.433 | 0.489 / 0.413 |
| 10 (1 word) | **0.873** | 1.730 |
| 12 (1 word) | **0.696** | 1.725 |
| 20 (2 words) | **1.065** | 2.086 |
| 33 (4 words) | 2.644 | 2.589 |
Fig 2: Ragged table comparing just NW templating vs not having it templated

So `NW` is templated for 0-3 words and left runtime from 4 up, which is where it stops paying.

## Results

AMD Genoa (EPYC, Zen 4), `mode/opt`, interleaved A/B against the parent commit, one code size per process, 250 samples per cell, min ns per (query, candidate) pair.

Word-multiple sizes:

| ncodes | AVX2 | AVX512 |
| --- | --- | --- |
| 32 | **-12.6%** (1.440 -> 1.258) | **-23.3%** (1.352 -> 1.037) |
| 64 | **-40.6%** (3.421 -> 2.031) | **-63.1%** (5.680 -> 2.094) |
| 40 | +0.0% | **-4.0%** (2.954 -> 2.837) |
| 48 | +0.1% | **-4.7%** (3.530 -> 3.365) |
| 56 | +0.0% | **+4.0%** (3.944 -> 4.101) |
| 8, 16, 24, 128, 160 | within +/-0.3% | within +/-0.8% |
Fig 3: result table comparing against baseline (aka before this diff)

ncodes 32 and 64 are the sizes whose codes are exactly one vector register wide, which is where the gather cost the most.

Absolute cost of every code size with this diff, so the ragged ones can be read against the word-multiple ones:

| ncodes | kind | AVX2 ns/pair | AVX2 ns/byte | AVX512 ns/pair | AVX512 ns/byte |
| --- | --- | --- | --- | --- | --- |
| 2 | ragged | 0.487 | 0.243 | 0.476 | 0.238 |
| 4 | ragged | 0.434 | 0.108 | 0.441 | 0.110 |
| 8 | word-multiple | 0.240 | 0.030 | 0.215 | 0.027 |
| 10 | ragged | 0.879 | 0.088 | 0.789 | 0.079 |
| 12 | ragged | 0.693 | 0.058 | 0.693 | 0.058 |
| 16 | word-multiple | 0.713 | 0.045 | 0.573 | 0.036 |
| 20 | ragged | 1.065 | 0.053 | 1.127 | 0.056 |
| 24 | word-multiple | 2.082 | 0.087 | 2.081 | 0.087 |
| 32 | word-multiple | 1.257 | 0.039 | 1.036 | 0.032 |
| 33 | ragged | 2.644 | 0.080 | 3.160 | 0.096 |
| 40 | word-multiple | 2.650 | 0.066 | 2.837 | 0.071 |
| 48 | word-multiple | 3.110 | 0.065 | 3.361 | 0.070 |
| 56 | word-multiple | 3.452 | 0.062 | 4.096 | 0.073 |
| 64 | word-multiple | 2.039 | 0.032 | 2.102 | 0.033 |
| 128 | word-multiple | 6.033 | 0.047 | 5.348 | 0.042 |
| 160 | word-multiple | 8.164 | 0.051 | 5.731 | 0.036 |
Fig 4: result table showing the ns/pair and ns/byte performance. ragged has no "before" because it is newly supported with this diff.

Read down the ns/byte columns and the sizes with their own kernel (8, 16, 32, 64) sit at 0.027-0.045, the new ragged kernel at 0.053-0.096 once past a single word, and the shared runtime-word-count kernel (24, 40, 48, 56, 128, 160) at 0.036-0.087. That last group is the weakest: ncodes 24 costs 2.082 ns/pair against 1.257 for ncodes 32, and ncodes 56 costs 3.452 against 2.039 for ncodes 64, both more expensive than a larger code. It vectorizes across candidates for the same reason described above, but its word count is not known at compile time so the same fix does not apply. Left alone here.

The two ragged sizes below one word (2 and 4) are dominated by fixed per-call work rather than the compare itself, which is why their ns/byte is high.

Reviewed By: alibeklfc

Differential Revision: D115982541

fbshipit-source-id: 081c600973fbdf30852527fb6beaae4e29cadfa3
2026-08-19 10:15:02 -07:00
Max BuckleyandClaude Opus 4.6 b3a2914982 fix: resolve compiler warnings in test files (#4944)
Summary:
- Fix compiler warnings in all 20 FAISS C++ test files
- Fixes include `-Wshadow`, `-Wunused-parameter`, and signed/unsigned comparisons

All changes are mechanical. No functional changes.

Part 13/13 of the compiler warnings cleanup (split from https://github.com/facebookresearch/faiss/issues/4810 per maintainer request).

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

Test Plan:
- [x] No functional changes — all fixes are mechanical renames and annotations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

Reviewed By: mnorris11

Differential Revision: D96996609

Pulled By: alibeklfc

fbshipit-source-id: 72555246a4ccf540eb5422e64018c8d5e2660dce
2026-03-20 10:21:55 -07:00
sdye 6da9b2d3a9 Fix compilation on macOS ARM64: Use faiss::idx_t instead of long test_hamming (#4755)
Summary:
There's a build failure triggered by vector types in test_hamming.cpp. On  macOS ARM64 long and int64_t are distinct types.
Here's a fix.

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

Reviewed By: subhadeepkaran

Differential Revision: D90632661

Pulled By: alibeklfc

fbshipit-source-id: 9c531bd8c80c7f090d0baa3115f97040885a09fe
2026-01-13 18:04:00 -08:00
generatedunixname89002005287564 de5e85ef04 Fix CQS signal. Id] 88153895 -- readability-redundant-string-init in fbcode/faiss (#4283)
Summary: Pull Request resolved: https://github.com/facebookresearch/faiss/pull/4283

Reviewed By: dtolnay

Differential Revision: D72700675

fbshipit-source-id: 631daa12b11b45e16aa5877b6d6f5556753ce53f
2025-04-09 08:29:18 -07:00
Lucian Grijincu 3d96ad56a4 faiss: fix non-templated hammings function (#4195)
Summary:
Pull Request resolved: https://github.com/facebookresearch/faiss/pull/4195

Non-templated `hammings` call produced incorrect values.

`hammings` is called from `hamming_distance_table`, which in turn is unused so no impact.
https://www.internalfb.com/code/fbsource/[85684614381d9bdfaaa0bb4a42e244296e350848]/fbcode/faiss/IndexPQ.cpp?lines=439-446

Reviewed By: gtwang01

Differential Revision: D69613329

fbshipit-source-id: 5d02a99b04492a61ebf0134f0c1719eac86fbb4f
2025-03-07 11:38:20 -08:00
George Wang 87a760e631 Add testing for utils/hamming.cpp (#4079)
Summary:
Pull Request resolved: https://github.com/facebookresearch/faiss/pull/4079

As title

Reviewed By: mengdilin

Differential Revision: D66976823

fbshipit-source-id: 71f8d261787a75d7ec59ad4bea6e27676e2410b5
2024-12-12 15:58:48 -08:00