mirror of
https://github.com/facebookresearch/faiss.git
synced 2026-10-11 22:50:00 +00:00
Summary:
Add a RISC-V Vector Extension implementation for the product-quantizer
distance-table computation, and wire it into the SIMD dispatch path. The
kernels are runtime vector-length aware (vsetvl-driven, e32m1) rather than
assuming a fixed vector width.
Changes
- compute_PQ_dis_tables_dsub2<RISCV_RVV> (utils/distances_rvv.cpp): RVV
kernel for both inner-product and L2 dsub=2 distance tables. Centroids are
first split into even/odd dimension planes (c0_all / c1_all) so each
subquantizer's ksub entries load as contiguous vectors; the hot loop
processes two subquantizers per iteration:
IP: r = c0*x0 + c1*x1 (vfmul + vfmacc)
L2: r = (c0-x0)^2 + (c1-x1)^2 (vfsub + vfmul + vfadd)
- Add RVV specializations for the accompanying distance primitives:
fvec_norm_L2sqr, fvec_L2sqr_ny, fvec_L2sqr_ny_transposed and
fvec_inner_products_ny are vectorized (strided vlse32 over ny); the
remaining fvec_* and VectorDistance specializations fall back to
SIMDLevel::NONE.
- compute_distance_tables / compute_inner_prod_tables: route dsub == 2 &&
nbits >= 3 && nbits < 8 through compute_PQ_dis_tables_dsub2 when
COMPILE_SIMD_RISCV_RVV is defined, matching the AVX2 / ARM_NEON fast path.
- compute_PQ_dis_tables_dsub2_dispatch: extend the dispatch mask to include
RISCV_RVV.
### Review fixes
The AI reviewers reported 6 valid defects. This version corrects all of them.
- The RVV `compute_PQ_dis_tables_dsub2` unrolled the subquantizer loop by 2
and had no tail. An odd `M` left the last `ksub` table entries unwritten.
The kernel now writes the last subquantizer in a tail loop.
- `fvec_inner_products_ny<RISCV_RVV>` loaded every chunk from `y + j`. The
base pointer missed the `i * d` offset. Each chunk after the first repeated
the first vectors. The kernel now loads from `y + i * d + j`.
- `fvec_norm_L2sqr<RISCV_RVV>` ran one `vfredusum` per vector chunk. A
`d=768` input at VLEN=128 cost 24 serialized reductions. The function again
keeps a tail-undisturbed accumulator, and it reduces once after the loop.
- The `dsub2` kernels require `ksub % 8 == 0`. The dispatch condition accepted
every `nbits < 8`, so an `nbits` of 1 or 2 made `compute_distance_tables`
throw. The condition now also requires `nbits >= 3`. This defect also
affected the AVX2 and the ARM NEON paths.
- `search_sdc` dropped the per-thread `q_row` pointer precomputation. That
change added `M` multiplications per database vector on every architecture.
This version restores the precomputation.
- The `ny` kernels fixed the chunk width at the VLEN=128 value. They now read
`VLMAX` with `vsetvlmax`. Hardware with a larger VLEN uses the whole vector
register.
The diff also removes 2 unreadable comments from the RVV kernel.
Caveat: the benchmark below ran on the previous version. That version included
the `search_sdc` change that this version reverts.
### Performance (sg2044)
Cmd: 1M 768 <PQ> 32 100 16 1 1000 1 <metric> (search ns/query, lower is better)
| # | CONFIG | M | NPROBE | METRIC | F1(ns) | PQ1(ns) | F2(ns) | PQ2(ns) | Δ_PQ% | Δ_F% |
|----|----------|-----|--------|--------|---------|---------|---------|---------|---------|--------|
| 01 | PQ8x4 | 4 | 8 | L2 | 6656870 | 536988 | 6582134 | 495970 | -7.64% | -1.12% |
| 02 | PQ8x4 | 4 | 8 | IP | 6228497 | 475320 | 6164081 | 481118 | +1.22% | -1.03% |
| 03 | PQ8x8 | 8 | 8 | L2 | 6626171 | 545230 | 6580586 | 500343 | -8.23% | -0.69% |
| 04 | PQ8x8 | 8 | 8 | IP | 6228452 | 519710 | 6165707 | 479864 | -7.67% | -1.01% |
| 05 | PQ8x16 | 16 | 8 | L2 | 6615305 | 697070 | 6582239 | 624059 | -10.47% | -0.50% |
| 06 | PQ8x16 | 16 | 8 | IP | 6227067 | 642949 | 6164572 | 587271 | -8.66% | -1.00% |
| 07 | PQ8x32 | 32 | 8 | L2 | 6624323 | 1185655 | 6581329 | 902298 | -23.90% | -0.65% |
| 08 | PQ8x32 | 32 | 8 | IP | 6239930 | 1155545 | 6164198 | 785403 | -32.03% | -1.21% |
| 09 | PQ8x64 | 64 | 8 | L2 | 6624196 | 2006275 | 6578815 | 1687191 | -15.90% | -0.69% |
| 10 | PQ8x64 | 64 | 8 | IP | 6225719 | 1800678 | 6164767 | 1512760 | -15.99% | -0.98% |
| 11 | PQ8x96 | 96 | 8 | L2 | 6617525 | 3108377 | 6582946 | 2701758 | -13.08% | -0.52% |
| 12 | PQ8x96 | 96 | 8 | IP | 6239711 | 2657958 | 6170006 | 2320803 | -12.68% | -1.12% |
| 13 | PQ8x384 | 384 | 8 | L2 | 6620602 | 14029275| 6583820 | 12318712| -12.19% | -0.56% |
| 14 | PQ8x384 | 384 | 8 | IP | 6228776 | 12089134| 6166143 | 10509785| -13.06% | -1.01% |
| 15 | PQ8x32 | 32 | 4 | L2 | 3321625 | 773663 | 3298874 | 491225 | -36.51% | -0.68% |
| 16 | PQ8x32 | 32 | 16 | L2 |13223858 | 2184906 |13152640 | 1645444 | -24.69% | -0.54% |
| 17 | PQ8x32 | 32 | 32 | L2 |26430233 | 3405062 |26331426 | 3138563 | -7.83% | -0.37% |
| 18 | PQ8x64 | 64 | 4 | L2 | 3320850 | 1191054 | 3298426 | 969309 | -18.62% | -0.68% |
| 19 | PQ8x64 | 64 | 16 | L2 |13207340 | 3944868 |13149902 | 3252654 | -17.55% | -0.43% |
| 20 | PQ8x64 | 64 | 32 | L2 |26467696 | 6725098 |26335327 | 6667493 | -0.86% | -0.50% |
| 21 | PQ8x16 | 16 | 4 | L2 | 3316496 | 427653 | 3304051 | 367407 | -14.09% | -0.38% |
| 22 | PQ8x16 | 16 | 16 | L2 |13203491 | 1198002 |13150061 | 1121421 | -6.39% | -0.40% |
| 23 | PQ8x16 | 16 | 32 | L2 |26438401 | 2199527 |26280149 | 2143973 | -2.53% | -0.60% |
Δ_PQ%: PQ baseline -> RVV; Δ_F%: Flat baseline -> RVV.
Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5543
Test Plan:
`buck build fbcode//faiss:faiss` passes.
`buck2 test` passes on the PQ and the distance C++ tests:
`test_disable_pq_sdc_tables`, `test_distances_dispatch`, `test_distances_simd`,
`test_ivfpq_codec`, `test_ivfpq_indexing`, `test_pq_encoding` and
`test_pq_code_distance`. The result is 27 pass and 0 fail. The host is aarch64,
so 8 tests skip. Those tests need a second SIMD level to compare against.
This diff adds 2 tests to `faiss/tests/test_product_quantizer.py`:
- `test_dsub2_4bit` covers `dsub=2` with `nbits=4`. `ksub` is 16, so the SIMD
`dsub2` kernel runs.
- `test_dsub2_2bit` covers `dsub=2` with `nbits=2`. `ksub` is 4, so the generic
path must run instead.
I checked the `nbits >= 3` fix with a temporary C++ test on ARM NEON. The test
compares `compute_distance_tables` and `compute_inner_prod_tables` against
`compute_distance_table` and `compute_inner_prod_table`, for `nbits` of 1, 2, 3
and 4. The test fails with a `ksub % 8 == 0` assertion before the fix. The test
passes after the fix. I removed the temporary test before submit.
Limits of the local verification:
- I did not run `faiss/tests/test_product_quantizer.py` on this devserver. The
`fbcode//faiss/python:pyfaiss-py-gen` SWIG step fails on aarch64 with
`Exec format error`. The failure also reproduces without my changes. CI runs
the Python tests on x86.
- I did not compile `utils/simd_impl/distances_rvv.cpp`. This devserver has no
RISC-V toolchain. `xplat.bzl` registers no RVV sources, so Buck and internal
CI do not build that file either. Only the OSS CMake build on rv64 compiles
it. A reviewer with RVV hardware should confirm the 4 RVV kernel fixes.
Reviewed By: juancarpio27
Differential Revision: D117914440
Pulled By: mnorris11
fbshipit-source-id: 3539a116d47f4a96c1051bc10e6440deae8c5302
576 lines
20 KiB
C++
576 lines
20 KiB
C++
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
// -*- c++ -*-
|
|
|
|
#include <faiss/utils/distances.h>
|
|
|
|
#ifdef COMPILE_SIMD_RISCV_RVV
|
|
|
|
#include <faiss/utils/extra_distances.h>
|
|
#include <riscv_vector.h>
|
|
#include <vector>
|
|
|
|
namespace faiss {
|
|
|
|
template <typename Vec, typename Reduce>
|
|
static inline float rvv_reduce(
|
|
Vec value,
|
|
size_t vl,
|
|
float identity,
|
|
Reduce reduce) {
|
|
vfloat32m1_t init = __riscv_vfmv_s_f_f32m1(identity, 1);
|
|
vfloat32m1_t result = reduce(value, init, vl);
|
|
return __riscv_vfmv_f_s_f32m1_f32(result);
|
|
}
|
|
|
|
static inline size_t rvv_argmin(const float* values, size_t n) {
|
|
size_t vlmax = __riscv_vsetvlmax_e32m8();
|
|
vfloat32m8_t vmin = __riscv_vfmv_v_f_f32m8(__builtin_inff(), vlmax);
|
|
size_t i = 0;
|
|
while (i < n) {
|
|
size_t vl = __riscv_vsetvl_e32m8(n - i);
|
|
vfloat32m8_t vd = __riscv_vle32_v_f32m8(values + i, vl);
|
|
vmin = __riscv_vfmin_vv_f32m8_tu(vmin, vmin, vd, vl);
|
|
i += vl;
|
|
}
|
|
float min_val = rvv_reduce(
|
|
vmin, vlmax, __builtin_inff(), __riscv_vfredmin_vs_f32m8_f32m1);
|
|
i = 0;
|
|
while (i < n) {
|
|
size_t vl = __riscv_vsetvl_e32m8(n - i);
|
|
vfloat32m8_t vd = __riscv_vle32_v_f32m8(values + i, vl);
|
|
long j = __riscv_vfirst_m_b4(
|
|
__riscv_vmfeq_vf_f32m8_b4(vd, min_val, vl), vl);
|
|
if (j >= 0)
|
|
return i + static_cast<size_t>(j);
|
|
i += vl;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// Write the ksub table entries of one subquantizer that has dsub = 2.
|
|
// The function computes one of these 2 results:
|
|
// inner product: out[k] = x0 * c0[k] + x1 * c1[k]
|
|
// L2: out[k] = (c0[k] - x0)^2 + (c1[k] - x1)^2
|
|
template <bool is_inner_product>
|
|
static inline void rvv_pq2_row(
|
|
const float* c0,
|
|
const float* c1,
|
|
float x0,
|
|
float x1,
|
|
size_t ksub,
|
|
size_t vl,
|
|
float* out) {
|
|
for (size_t k = 0; k < ksub; k += vl) {
|
|
vfloat32m1_t vc0 = __riscv_vle32_v_f32m1(c0 + k, vl);
|
|
vfloat32m1_t vc1 = __riscv_vle32_v_f32m1(c1 + k, vl);
|
|
if constexpr (is_inner_product) {
|
|
vfloat32m1_t r = __riscv_vfmacc_vf_f32m1(
|
|
__riscv_vfmul_vf_f32m1(vc0, x0, vl), x1, vc1, vl);
|
|
__riscv_vse32_v_f32m1(out + k, r, vl);
|
|
} else {
|
|
vfloat32m1_t d0 = __riscv_vfsub_vf_f32m1(vc0, x0, vl);
|
|
vfloat32m1_t d1 = __riscv_vfsub_vf_f32m1(vc1, x1, vl);
|
|
d0 = __riscv_vfmul_vv_f32m1(d0, d0, vl);
|
|
d1 = __riscv_vfmul_vv_f32m1(d1, d1, vl);
|
|
vfloat32m1_t r = __riscv_vfadd_vv_f32m1(d0, d1, vl);
|
|
__riscv_vse32_v_f32m1(out + k, r, vl);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <bool is_inner_product>
|
|
static void rvv_pq2_tables(
|
|
size_t M,
|
|
size_t ksub,
|
|
const float* c0_all,
|
|
const float* c1_all,
|
|
size_t nx,
|
|
const float* x,
|
|
size_t vl,
|
|
float* dis_tables) {
|
|
const size_t d = 2 * M;
|
|
for (size_t i = 0; i < nx; i++) {
|
|
const float* xi = x + i * d;
|
|
float* oi = dis_tables + i * M * ksub;
|
|
size_t m = 0;
|
|
// The loop computes 2 subquantizers per iteration. The 2 computations
|
|
// are independent, so the processor can run them at the same time.
|
|
for (; m + 1 < M; m += 2) {
|
|
rvv_pq2_row<is_inner_product>(
|
|
c0_all + m * ksub,
|
|
c1_all + m * ksub,
|
|
xi[2 * m],
|
|
xi[2 * m + 1],
|
|
ksub,
|
|
vl,
|
|
oi + m * ksub);
|
|
rvv_pq2_row<is_inner_product>(
|
|
c0_all + (m + 1) * ksub,
|
|
c1_all + (m + 1) * ksub,
|
|
xi[2 * (m + 1)],
|
|
xi[2 * (m + 1) + 1],
|
|
ksub,
|
|
vl,
|
|
oi + (m + 1) * ksub);
|
|
}
|
|
// If M is odd, the loop above skips the last subquantizer. This loop
|
|
// computes it.
|
|
for (; m < M; m++) {
|
|
rvv_pq2_row<is_inner_product>(
|
|
c0_all + m * ksub,
|
|
c1_all + m * ksub,
|
|
xi[2 * m],
|
|
xi[2 * m + 1],
|
|
ksub,
|
|
vl,
|
|
oi + m * ksub);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <>
|
|
void compute_PQ_dis_tables_dsub2<SIMDLevel::RISCV_RVV>(
|
|
size_t d,
|
|
size_t ksub,
|
|
const float* all_centroids,
|
|
size_t nx,
|
|
const float* x,
|
|
bool is_inner_product,
|
|
float* dis_tables) {
|
|
const size_t M = d / 2;
|
|
FAISS_THROW_IF_NOT(ksub % 8 == 0);
|
|
|
|
// Separate the centroids into 2 planes. Each plane holds one component of
|
|
// every centroid, so a unit-stride load can read the plane.
|
|
std::vector<float> c0_all(M * ksub);
|
|
std::vector<float> c1_all(M * ksub);
|
|
for (size_t m = 0; m < M; m++) {
|
|
const float* cm = all_centroids + m * ksub * 2;
|
|
float* c0m = c0_all.data() + m * ksub;
|
|
float* c1m = c1_all.data() + m * ksub;
|
|
for (size_t k = 0; k < ksub; k++) {
|
|
c0m[k] = cm[2 * k];
|
|
c1m[k] = cm[2 * k + 1];
|
|
}
|
|
}
|
|
|
|
// ksub is a power of 2, and ksub is a multiple of 8. Therefore vl divides
|
|
// ksub exactly.
|
|
const size_t vl = __riscv_vsetvl_e32m1(ksub);
|
|
|
|
if (is_inner_product) {
|
|
rvv_pq2_tables<true>(
|
|
M, ksub, c0_all.data(), c1_all.data(), nx, x, vl, dis_tables);
|
|
} else {
|
|
rvv_pq2_tables<false>(
|
|
M, ksub, c0_all.data(), c1_all.data(), nx, x, vl, dis_tables);
|
|
}
|
|
}
|
|
|
|
template <>
|
|
float fvec_norm_L2sqr<SIMDLevel::RISCV_RVV>(const float* x, size_t d) {
|
|
size_t vlmax = __riscv_vsetvlmax_e32m8();
|
|
vfloat32m8_t acc = __riscv_vfmv_v_f_f32m8(0.0f, vlmax);
|
|
size_t i = 0;
|
|
while (i < d) {
|
|
size_t vl = __riscv_vsetvl_e32m8(d - i);
|
|
vfloat32m8_t vx = __riscv_vle32_v_f32m8(x + i, vl);
|
|
acc = __riscv_vfmacc_vv_f32m8_tu(acc, vx, vx, vl);
|
|
i += vl;
|
|
}
|
|
return rvv_reduce(acc, vlmax, 0.0f, __riscv_vfredusum_vs_f32m8_f32m1);
|
|
}
|
|
|
|
template <>
|
|
float fvec_L2sqr<SIMDLevel::RISCV_RVV>(
|
|
const float* x,
|
|
const float* y,
|
|
size_t d) {
|
|
size_t vlmax = __riscv_vsetvlmax_e32m8();
|
|
vfloat32m8_t acc = __riscv_vfmv_v_f_f32m8(0.0f, vlmax);
|
|
size_t i = 0;
|
|
while (i < d) {
|
|
size_t vl = __riscv_vsetvl_e32m8(d - i);
|
|
vfloat32m8_t vx = __riscv_vle32_v_f32m8(x + i, vl);
|
|
vfloat32m8_t vy = __riscv_vle32_v_f32m8(y + i, vl);
|
|
vx = __riscv_vfsub_vv_f32m8(vx, vy, vl);
|
|
acc = __riscv_vfmacc_vv_f32m8_tu(acc, vx, vx, vl);
|
|
i += vl;
|
|
}
|
|
return rvv_reduce(acc, vlmax, 0.0f, __riscv_vfredusum_vs_f32m8_f32m1);
|
|
}
|
|
|
|
template <>
|
|
float fvec_inner_product<SIMDLevel::RISCV_RVV>(
|
|
const float* x,
|
|
const float* y,
|
|
size_t d) {
|
|
size_t vlmax = __riscv_vsetvlmax_e32m8();
|
|
vfloat32m8_t acc = __riscv_vfmv_v_f_f32m8(0.0f, vlmax);
|
|
size_t i = 0;
|
|
while (i < d) {
|
|
size_t vl = __riscv_vsetvl_e32m8(d - i);
|
|
vfloat32m8_t vx = __riscv_vle32_v_f32m8(x + i, vl);
|
|
vfloat32m8_t vy = __riscv_vle32_v_f32m8(y + i, vl);
|
|
acc = __riscv_vfmacc_vv_f32m8_tu(acc, vx, vy, vl);
|
|
i += vl;
|
|
}
|
|
return rvv_reduce(acc, vlmax, 0.0f, __riscv_vfredusum_vs_f32m8_f32m1);
|
|
}
|
|
|
|
template <>
|
|
float fvec_L1<SIMDLevel::RISCV_RVV>(const float* x, const float* y, size_t d) {
|
|
size_t vlmax = __riscv_vsetvlmax_e32m8();
|
|
vfloat32m8_t acc = __riscv_vfmv_v_f_f32m8(0.0f, vlmax);
|
|
size_t i = 0;
|
|
while (i < d) {
|
|
size_t vl = __riscv_vsetvl_e32m8(d - i);
|
|
vfloat32m8_t vx = __riscv_vle32_v_f32m8(x + i, vl);
|
|
vfloat32m8_t vy = __riscv_vle32_v_f32m8(y + i, vl);
|
|
vx = __riscv_vfsub_vv_f32m8(vx, vy, vl);
|
|
vx = __riscv_vfsgnjx_vv_f32m8(vx, vx, vl);
|
|
acc = __riscv_vfadd_vv_f32m8_tu(acc, acc, vx, vl);
|
|
i += vl;
|
|
}
|
|
return rvv_reduce(acc, vlmax, 0.0f, __riscv_vfredusum_vs_f32m8_f32m1);
|
|
}
|
|
|
|
template <>
|
|
float fvec_Linf<SIMDLevel::RISCV_RVV>(
|
|
const float* x,
|
|
const float* y,
|
|
size_t d) {
|
|
size_t vlmax = __riscv_vsetvlmax_e32m8();
|
|
vfloat32m8_t vmax = __riscv_vfmv_v_f_f32m8(0.0f, vlmax);
|
|
size_t i = 0;
|
|
while (i < d) {
|
|
size_t vl = __riscv_vsetvl_e32m8(d - i);
|
|
vfloat32m8_t vx = __riscv_vle32_v_f32m8(x + i, vl);
|
|
vfloat32m8_t vy = __riscv_vle32_v_f32m8(y + i, vl);
|
|
vx = __riscv_vfsub_vv_f32m8(vx, vy, vl);
|
|
vx = __riscv_vfsgnjx_vv_f32m8(vx, vx, vl);
|
|
vmax = __riscv_vfmax_vv_f32m8_tu(vmax, vmax, vx, vl);
|
|
i += vl;
|
|
}
|
|
return rvv_reduce(vmax, vlmax, 0.0f, __riscv_vfredmax_vs_f32m8_f32m1);
|
|
}
|
|
|
|
template <>
|
|
void fvec_inner_product_batch_4<SIMDLevel::RISCV_RVV>(
|
|
const float* x,
|
|
const float* y0,
|
|
const float* y1,
|
|
const float* y2,
|
|
const float* y3,
|
|
const size_t d,
|
|
float& dis0,
|
|
float& dis1,
|
|
float& dis2,
|
|
float& dis3) {
|
|
size_t vlmax = __riscv_vsetvlmax_e32m4();
|
|
vfloat32m4_t vacc0 = __riscv_vfmv_v_f_f32m4(0.0f, vlmax);
|
|
vfloat32m4_t vacc1 = __riscv_vfmv_v_f_f32m4(0.0f, vlmax);
|
|
vfloat32m4_t vacc2 = __riscv_vfmv_v_f_f32m4(0.0f, vlmax);
|
|
vfloat32m4_t vacc3 = __riscv_vfmv_v_f_f32m4(0.0f, vlmax);
|
|
size_t i = 0;
|
|
while (i < d) {
|
|
size_t vl = __riscv_vsetvl_e32m4(d - i);
|
|
vfloat32m4_t vx = __riscv_vle32_v_f32m4(x + i, vl);
|
|
vfloat32m4_t vy = __riscv_vle32_v_f32m4(y0 + i, vl);
|
|
vacc0 = __riscv_vfmacc_vv_f32m4_tu(vacc0, vx, vy, vl);
|
|
vy = __riscv_vle32_v_f32m4(y1 + i, vl);
|
|
vacc1 = __riscv_vfmacc_vv_f32m4_tu(vacc1, vx, vy, vl);
|
|
vy = __riscv_vle32_v_f32m4(y2 + i, vl);
|
|
vacc2 = __riscv_vfmacc_vv_f32m4_tu(vacc2, vx, vy, vl);
|
|
vy = __riscv_vle32_v_f32m4(y3 + i, vl);
|
|
vacc3 = __riscv_vfmacc_vv_f32m4_tu(vacc3, vx, vy, vl);
|
|
i += vl;
|
|
}
|
|
dis0 = rvv_reduce(vacc0, vlmax, 0.0f, __riscv_vfredusum_vs_f32m4_f32m1);
|
|
dis1 = rvv_reduce(vacc1, vlmax, 0.0f, __riscv_vfredusum_vs_f32m4_f32m1);
|
|
dis2 = rvv_reduce(vacc2, vlmax, 0.0f, __riscv_vfredusum_vs_f32m4_f32m1);
|
|
dis3 = rvv_reduce(vacc3, vlmax, 0.0f, __riscv_vfredusum_vs_f32m4_f32m1);
|
|
}
|
|
|
|
template <>
|
|
void fvec_L2sqr_batch_4<SIMDLevel::RISCV_RVV>(
|
|
const float* x,
|
|
const float* y0,
|
|
const float* y1,
|
|
const float* y2,
|
|
const float* y3,
|
|
const size_t d,
|
|
float& dis0,
|
|
float& dis1,
|
|
float& dis2,
|
|
float& dis3) {
|
|
size_t vlmax = __riscv_vsetvlmax_e32m4();
|
|
vfloat32m4_t vacc0 = __riscv_vfmv_v_f_f32m4(0.0f, vlmax);
|
|
vfloat32m4_t vacc1 = __riscv_vfmv_v_f_f32m4(0.0f, vlmax);
|
|
vfloat32m4_t vacc2 = __riscv_vfmv_v_f_f32m4(0.0f, vlmax);
|
|
vfloat32m4_t vacc3 = __riscv_vfmv_v_f_f32m4(0.0f, vlmax);
|
|
size_t i = 0;
|
|
while (i < d) {
|
|
size_t vl = __riscv_vsetvl_e32m4(d - i);
|
|
vfloat32m4_t vx = __riscv_vle32_v_f32m4(x + i, vl);
|
|
vfloat32m4_t vy = __riscv_vle32_v_f32m4(y0 + i, vl);
|
|
vy = __riscv_vfsub_vv_f32m4(vx, vy, vl);
|
|
vacc0 = __riscv_vfmacc_vv_f32m4_tu(vacc0, vy, vy, vl);
|
|
vy = __riscv_vle32_v_f32m4(y1 + i, vl);
|
|
vy = __riscv_vfsub_vv_f32m4(vx, vy, vl);
|
|
vacc1 = __riscv_vfmacc_vv_f32m4_tu(vacc1, vy, vy, vl);
|
|
vy = __riscv_vle32_v_f32m4(y2 + i, vl);
|
|
vy = __riscv_vfsub_vv_f32m4(vx, vy, vl);
|
|
vacc2 = __riscv_vfmacc_vv_f32m4_tu(vacc2, vy, vy, vl);
|
|
vy = __riscv_vle32_v_f32m4(y3 + i, vl);
|
|
vy = __riscv_vfsub_vv_f32m4(vx, vy, vl);
|
|
vacc3 = __riscv_vfmacc_vv_f32m4_tu(vacc3, vy, vy, vl);
|
|
i += vl;
|
|
}
|
|
dis0 = rvv_reduce(vacc0, vlmax, 0.0f, __riscv_vfredusum_vs_f32m4_f32m1);
|
|
dis1 = rvv_reduce(vacc1, vlmax, 0.0f, __riscv_vfredusum_vs_f32m4_f32m1);
|
|
dis2 = rvv_reduce(vacc2, vlmax, 0.0f, __riscv_vfredusum_vs_f32m4_f32m1);
|
|
dis3 = rvv_reduce(vacc3, vlmax, 0.0f, __riscv_vfredusum_vs_f32m4_f32m1);
|
|
}
|
|
|
|
template <>
|
|
void fvec_L2sqr_ny_transposed<SIMDLevel::RISCV_RVV>(
|
|
float* dis,
|
|
const float* x,
|
|
const float* y,
|
|
const float* y_sqlen,
|
|
size_t d,
|
|
size_t d_offset,
|
|
size_t ny) {
|
|
// Compute the squared length of the query subvector.
|
|
float x_sqlen = 0;
|
|
for (size_t j = 0; j < d; j++) {
|
|
x_sqlen += x[j] * x[j];
|
|
}
|
|
|
|
// The loop below divides the ny dimension into chunks. Each chunk uses
|
|
// e32m8. The chunk size is VLMAX, so the kernel fills the whole vector
|
|
// register at every VLEN.
|
|
const size_t chunk = __riscv_vsetvlmax_e32m8();
|
|
size_t i = 0;
|
|
|
|
// dis[i] = x_sqlen + y_sqlen[i] - 2 * sum_j x[j] * y[j * d_offset + i]
|
|
//
|
|
// The code accumulates the dot product first, and it applies the factor
|
|
// of 2 afterwards. A factor of -2 on x[j] can overflow to an infinity.
|
|
// That infinity then multiplies an element of y that is 0, and the
|
|
// product is a NaN. The SIMDLevel::NONE implementation scales the
|
|
// accumulated dot product in the same way.
|
|
for (; i + chunk <= ny; i += chunk) {
|
|
vfloat32m8_t dp = __riscv_vfmv_v_f_f32m8(0.0f, chunk);
|
|
for (size_t j = 0; j < d; j++) {
|
|
vfloat32m8_t y_vec =
|
|
__riscv_vle32_v_f32m8(y + j * d_offset + i, chunk);
|
|
dp = __riscv_vfmacc_vf_f32m8(dp, x[j], y_vec, chunk);
|
|
}
|
|
|
|
vfloat32m8_t acc = __riscv_vle32_v_f32m8(y_sqlen + i, chunk);
|
|
acc = __riscv_vfadd_vf_f32m8(acc, x_sqlen, chunk);
|
|
dp = __riscv_vfmul_vf_f32m8(dp, 2.0f, chunk);
|
|
acc = __riscv_vfsub_vv_f32m8(acc, dp, chunk);
|
|
|
|
__riscv_vse32_v_f32m8(dis + i, acc, chunk);
|
|
}
|
|
|
|
// This block computes the last ny % chunk elements.
|
|
if (i < ny) {
|
|
size_t vl = __riscv_vsetvl_e32m8(ny - i);
|
|
|
|
vfloat32m8_t dp = __riscv_vfmv_v_f_f32m8(0.0f, vl);
|
|
for (size_t j = 0; j < d; j++) {
|
|
vfloat32m8_t y_vec =
|
|
__riscv_vle32_v_f32m8(y + j * d_offset + i, vl);
|
|
dp = __riscv_vfmacc_vf_f32m8(dp, x[j], y_vec, vl);
|
|
}
|
|
|
|
vfloat32m8_t acc = __riscv_vle32_v_f32m8(y_sqlen + i, vl);
|
|
acc = __riscv_vfadd_vf_f32m8(acc, x_sqlen, vl);
|
|
dp = __riscv_vfmul_vf_f32m8(dp, 2.0f, vl);
|
|
acc = __riscv_vfsub_vv_f32m8(acc, dp, vl);
|
|
|
|
__riscv_vse32_v_f32m8(dis + i, acc, vl);
|
|
}
|
|
}
|
|
|
|
template <>
|
|
void fvec_inner_products_ny<SIMDLevel::RISCV_RVV>(
|
|
float* ip,
|
|
const float* x,
|
|
const float* y,
|
|
size_t d,
|
|
size_t ny) {
|
|
// The loop below divides the ny dimension into chunks. Each chunk uses
|
|
// e32m4. An LMUL sweep showed that e32m4 is the fastest choice. It
|
|
// balances the throughput of the strided load vlse32 against the use of
|
|
// the register file. That load uses a stride of d*4 bytes. The chunk size
|
|
// is VLMAX, so the kernel fills the whole vector register at every VLEN.
|
|
// ip[i] = sum_{j=0}^{d-1} x[j] * y[i * d + j]
|
|
const size_t chunk = __riscv_vsetvlmax_e32m4();
|
|
const ptrdiff_t stride_bytes = (ptrdiff_t)d * (ptrdiff_t)sizeof(float);
|
|
size_t i = 0;
|
|
|
|
for (; i + chunk <= ny; i += chunk) {
|
|
vfloat32m4_t acc = __riscv_vfmv_v_f_f32m4(0.0f, chunk);
|
|
const float* yb = y + i * d; // the first vector of this chunk
|
|
|
|
for (size_t j = 0; j < d; j++) {
|
|
vfloat32m4_t y_vec =
|
|
__riscv_vlse32_v_f32m4(yb + j, stride_bytes, chunk);
|
|
acc = __riscv_vfmacc_vf_f32m4(acc, x[j], y_vec, chunk);
|
|
}
|
|
|
|
__riscv_vse32_v_f32m4(ip + i, acc, chunk);
|
|
}
|
|
|
|
// This block computes the last ny % chunk elements.
|
|
if (i < ny) {
|
|
size_t vl = __riscv_vsetvl_e32m4(ny - i);
|
|
|
|
vfloat32m4_t acc = __riscv_vfmv_v_f_f32m4(0.0f, vl);
|
|
const float* yb = y + i * d;
|
|
|
|
for (size_t j = 0; j < d; j++) {
|
|
vfloat32m4_t y_vec =
|
|
__riscv_vlse32_v_f32m4(yb + j, stride_bytes, vl);
|
|
acc = __riscv_vfmacc_vf_f32m4(acc, x[j], y_vec, vl);
|
|
}
|
|
|
|
__riscv_vse32_v_f32m4(ip + i, acc, vl);
|
|
}
|
|
}
|
|
|
|
template <>
|
|
void fvec_L2sqr_ny<SIMDLevel::RISCV_RVV>(
|
|
float* dis,
|
|
const float* x,
|
|
const float* y,
|
|
size_t d,
|
|
size_t ny) {
|
|
// The loop below divides the ny dimension into chunks. Each chunk uses
|
|
// e32m8. The chunk size is VLMAX, so the kernel fills the whole vector
|
|
// register at every VLEN.
|
|
const size_t chunk = __riscv_vsetvlmax_e32m8();
|
|
const ptrdiff_t stride_bytes = (ptrdiff_t)d * (ptrdiff_t)sizeof(float);
|
|
size_t i = 0;
|
|
|
|
for (; i + chunk <= ny; i += chunk) {
|
|
vfloat32m8_t acc = __riscv_vfmv_v_f_f32m8(0.0f, chunk);
|
|
const float* yb = y + i * d; // the first vector of this chunk
|
|
for (size_t j = 0; j < d; j++) {
|
|
vfloat32m8_t y_vec =
|
|
__riscv_vlse32_v_f32m8(yb + j, stride_bytes, chunk);
|
|
vfloat32m8_t diff = __riscv_vfsub_vf_f32m8(y_vec, x[j], chunk);
|
|
acc = __riscv_vfmacc_vv_f32m8(acc, diff, diff, chunk);
|
|
}
|
|
__riscv_vse32_v_f32m8(dis + i, acc, chunk);
|
|
}
|
|
|
|
if (i < ny) {
|
|
size_t vl = __riscv_vsetvl_e32m8(ny - i);
|
|
vfloat32m8_t acc = __riscv_vfmv_v_f_f32m8(0.0f, vl);
|
|
const float* yb = y + i * d;
|
|
for (size_t j = 0; j < d; j++) {
|
|
vfloat32m8_t y_vec =
|
|
__riscv_vlse32_v_f32m8(yb + j, stride_bytes, vl);
|
|
vfloat32m8_t diff = __riscv_vfsub_vf_f32m8(y_vec, x[j], vl);
|
|
acc = __riscv_vfmacc_vv_f32m8(acc, diff, diff, vl);
|
|
}
|
|
__riscv_vse32_v_f32m8(dis + i, acc, vl);
|
|
}
|
|
}
|
|
|
|
template <>
|
|
size_t fvec_L2sqr_ny_nearest<SIMDLevel::RISCV_RVV>(
|
|
float* distances_tmp_buffer,
|
|
const float* x,
|
|
const float* y,
|
|
size_t d,
|
|
size_t ny) {
|
|
return fvec_L2sqr_ny_nearest<SIMDLevel::NONE>(
|
|
distances_tmp_buffer, x, y, d, ny);
|
|
}
|
|
|
|
template <>
|
|
size_t fvec_L2sqr_ny_nearest_y_transposed<SIMDLevel::RISCV_RVV>(
|
|
float* distances_tmp_buffer,
|
|
const float* x,
|
|
const float* y,
|
|
const float* y_sqlen,
|
|
size_t d,
|
|
size_t d_offset,
|
|
size_t ny) {
|
|
return fvec_L2sqr_ny_nearest_y_transposed<SIMDLevel::NONE>(
|
|
distances_tmp_buffer, x, y, y_sqlen, d, d_offset, ny);
|
|
}
|
|
|
|
template <>
|
|
void fvec_madd<SIMDLevel::RISCV_RVV>(
|
|
size_t n,
|
|
const float* a,
|
|
float bf,
|
|
const float* b,
|
|
float* c) {
|
|
size_t i = 0;
|
|
while (i < n) {
|
|
size_t vl = __riscv_vsetvl_e32m8(n - i);
|
|
vfloat32m8_t va = __riscv_vle32_v_f32m8(a + i, vl);
|
|
vfloat32m8_t vb = __riscv_vle32_v_f32m8(b + i, vl);
|
|
va = __riscv_vfmacc_vf_f32m8(va, bf, vb, vl);
|
|
__riscv_vse32_v_f32m8(c + i, va, vl);
|
|
i += vl;
|
|
}
|
|
}
|
|
|
|
template <>
|
|
int fvec_madd_and_argmin<SIMDLevel::RISCV_RVV>(
|
|
size_t n,
|
|
const float* a,
|
|
float bf,
|
|
const float* b,
|
|
float* c) {
|
|
fvec_madd<SIMDLevel::RISCV_RVV>(n, a, bf, b, c);
|
|
const size_t j = rvv_argmin(c, n);
|
|
return j < n ? static_cast<int>(j) : -1;
|
|
}
|
|
|
|
#define DEFINE_VECTOR_DISTANCE_RVV_FALLBACK(metric) \
|
|
template <> \
|
|
float VectorDistance<metric, SIMDLevel::RISCV_RVV>::operator()( \
|
|
const float* x, const float* y) const { \
|
|
return VectorDistance<metric, SIMDLevel::NONE>( \
|
|
this->d, this->metric_arg)(x, y); \
|
|
}
|
|
|
|
DEFINE_VECTOR_DISTANCE_RVV_FALLBACK(METRIC_L2)
|
|
DEFINE_VECTOR_DISTANCE_RVV_FALLBACK(METRIC_INNER_PRODUCT)
|
|
DEFINE_VECTOR_DISTANCE_RVV_FALLBACK(METRIC_L1)
|
|
DEFINE_VECTOR_DISTANCE_RVV_FALLBACK(METRIC_Linf)
|
|
DEFINE_VECTOR_DISTANCE_RVV_FALLBACK(METRIC_Lp)
|
|
DEFINE_VECTOR_DISTANCE_RVV_FALLBACK(METRIC_Canberra)
|
|
DEFINE_VECTOR_DISTANCE_RVV_FALLBACK(METRIC_BrayCurtis)
|
|
DEFINE_VECTOR_DISTANCE_RVV_FALLBACK(METRIC_JensenShannon)
|
|
DEFINE_VECTOR_DISTANCE_RVV_FALLBACK(METRIC_Jaccard)
|
|
DEFINE_VECTOR_DISTANCE_RVV_FALLBACK(METRIC_NaNEuclidean)
|
|
DEFINE_VECTOR_DISTANCE_RVV_FALLBACK(METRIC_GOWER)
|
|
|
|
#undef DEFINE_VECTOR_DISTANCE_RVV_FALLBACK
|
|
|
|
} // namespace faiss
|
|
|
|
#define THE_SIMD_LEVEL SIMDLevel::RISCV_RVV
|
|
// NOLINTNEXTLINE(facebook-hte-InlineHeader)
|
|
#include <faiss/utils/simd_impl/IVFFlatScanner-inl.h>
|
|
|
|
#endif // COMPILE_SIMD_RISCV_RVV
|