mirror of
https://github.com/facebookresearch/faiss.git
synced 2026-10-12 02:13:36 +00:00
feat(rvv): add vector arithmetic kernels (#5655)
Summary: On RISC-V, `fvec_add` and `fvec_sub` dispatch through `with_simd_level_256bit`. The level mask of that dispatcher (`AVAILABLE_SIMD_LEVELS_AVX2_NEON`) holds no `RISCV_RVV` bit. RVV hosts therefore use the scalar NONE implementation. This PR adds native RVV kernels for the two functions and fixes the dispatch. The kernels follow the vector-length-agnostic approach of the RVV kernels in the tree. The kernels use `__riscv_vsetvl` (m8) and adapt to any VLEN at runtime. The kernels assume no fixed vector width. ## Changes - Add RVV kernels in `faiss/utils/simd_impl/distances_rvv.cpp` for: - `fvec_add` (vector plus vector, and vector plus scalar) - `fvec_sub` - Add the `AVAILABLE_SIMD_LEVELS_BASE_NO_AVX512` mask in `faiss/impl/simd_dispatch.h`. - The mask holds NONE, AVX2, ARM_NEON, and RISCV_RVV. - The mask holds no AVX512 bit, so AVX512 machines keep the AVX2 path. - Route the three dispatchers (`fvec_sub`, `fvec_add`, scalar `fvec_add`) in `faiss/utils/distances_dispatch.h` through the new mask. - Stable already provides the `compute_PQ_dis_tables_dsub2` RVV kernel and the `ProductQuantizer` RISC-V path, so this PR does not change them. - This PR changes no behavior on other platforms. ## Performance The benchmark ran on the original patch. The original patch also held the `compute_PQ_dis_tables_dsub2` kernel. Measured on SG2044 (64-core RISC-V, RVV 1.0), Faiss C++ benchmark suite, rcq-search case: | Metric | Baseline | Patched | Change | |---|---|---|---| | instructions | 7.25 T | 6.87 T | -5.2% | | cycles | 4.60 T | 4.30 T | -6.5% | | wall time | 238.1 s | 223.6 s | -6.1% | | branches | 334.6 G | 305.0 G | -8.8% | The `fvec_add` hotspot in the perf record drops from 1.06% (scalar NONE) to 0.18% (RVV) of samples. ## Correctness - `sq-accuracy` reconstruction errors match the baseline on all 9 quantizer types. - `ndiff_for_idempotence` is 0. - The correctness run used the original patch. ### Co-authors - ihb2032 <[email protected]> - lyd1992 <[email protected]> - Yuansheng <[email protected]> Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5655 Test Plan: `buck2 build fbcode//faiss:faiss` passes. `buck2 test` passes on the distance tests `test_distances_dispatch` and `test_distances_simd`: 9 pass, 0 fail, 8 skip. The skips occur because the host provides one SIMD level. `buck2 test` passes on the PQ tests `test_pq_encoding`, `test_pq_code_distance`, `test_ivfpq_indexing`, and `test_disable_pq_sdc_tables`: 17 pass, 0 fail. Buck does not compile `faiss/utils/simd_impl/distances_rvv.cpp`. The host has no RISC-V toolchain. The new kernels still await review on RVV hardware. Reviewed By: alibeklfc Differential Revision: D121445525 Pulled By: mnorris11 fbshipit-source-id: ae3c1685e319bbb9523d87a3cbce8b1fd5310ce3
This commit is contained in:
committed by
meta-codesync[bot]
parent
7f6bf10b1c
commit
e24a371a70
@@ -36,6 +36,13 @@ constexpr int AVAILABLE_SIMD_LEVELS_AVX2_NEON = AVAILABLE_SIMD_LEVELS_NONE |
|
||||
constexpr int AVAILABLE_SIMD_LEVELS_BASE = AVAILABLE_SIMD_LEVELS_AVX2_NEON |
|
||||
(1 << int(SIMDLevel::AVX512)) | (1 << int(SIMDLevel::RISCV_RVV));
|
||||
|
||||
// BASE_NO_AVX512: BASE minus AVX512, for functions implemented at NONE,
|
||||
// AVX2, ARM_NEON and RISCV_RVV but with no AVX512 specialization. AVX512
|
||||
// machines fall through to AVX2, while RISC-V dispatches directly to the
|
||||
// native vector-length-agnostic RVV kernels.
|
||||
constexpr int AVAILABLE_SIMD_LEVELS_BASE_NO_AVX512 =
|
||||
AVAILABLE_SIMD_LEVELS_BASE & ~(1 << int(SIMDLevel::AVX512));
|
||||
|
||||
// BASE_WITH_VPOPCNT: BASE + AVX512_VPOPCNT, for kernels that need only
|
||||
// VPOPCNTDQ on top of baseline AVX-512 (Ice Lake, Zen 4, Zen 5).
|
||||
constexpr int AVAILABLE_SIMD_LEVELS_BASE_WITH_VPOPCNT =
|
||||
|
||||
@@ -184,7 +184,7 @@ inline void fvec_sub_dispatch(
|
||||
const float* a,
|
||||
const float* b,
|
||||
float* c) {
|
||||
with_simd_level_256bit(
|
||||
with_selected_simd_levels<AVAILABLE_SIMD_LEVELS_BASE_NO_AVX512>(
|
||||
[&]<SIMDLevel level>() { fvec_sub<level>(d, a, b, c); });
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ inline void fvec_add_dispatch(
|
||||
const float* a,
|
||||
const float* b,
|
||||
float* c) {
|
||||
with_simd_level_256bit(
|
||||
with_selected_simd_levels<AVAILABLE_SIMD_LEVELS_BASE_NO_AVX512>(
|
||||
[&]<SIMDLevel level>() { fvec_add<level>(d, a, b, c); });
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ inline void fvec_add_scalar_dispatch(
|
||||
const float* a,
|
||||
float b,
|
||||
float* c) {
|
||||
with_simd_level_256bit(
|
||||
with_selected_simd_levels<AVAILABLE_SIMD_LEVELS_BASE_NO_AVX512>(
|
||||
[&]<SIMDLevel level>() { fvec_add<level>(d, a, b, c); });
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#ifdef COMPILE_SIMD_RISCV_RVV
|
||||
|
||||
#include <faiss/impl/FaissAssert.h>
|
||||
#include <faiss/utils/extra_distances.h>
|
||||
#include <riscv_vector.h>
|
||||
#include <vector>
|
||||
@@ -363,9 +364,7 @@ void fvec_L2sqr_ny_transposed<SIMDLevel::RISCV_RVV>(
|
||||
// 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
|
||||
// of 2 afterwards. Matches SIMDLevel::NONE implementation which 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);
|
||||
@@ -411,10 +410,7 @@ void fvec_inner_products_ny<SIMDLevel::RISCV_RVV>(
|
||||
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.
|
||||
// e32m4.
|
||||
// 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);
|
||||
@@ -458,8 +454,7 @@ void fvec_L2sqr_ny<SIMDLevel::RISCV_RVV>(
|
||||
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.
|
||||
// e32m8.
|
||||
const size_t chunk = __riscv_vsetvlmax_e32m8();
|
||||
const ptrdiff_t stride_bytes = (ptrdiff_t)d * (ptrdiff_t)sizeof(float);
|
||||
size_t i = 0;
|
||||
@@ -544,6 +539,56 @@ int fvec_madd_and_argmin<SIMDLevel::RISCV_RVV>(
|
||||
return j < n ? static_cast<int>(j) : -1;
|
||||
}
|
||||
|
||||
template <>
|
||||
void fvec_add<SIMDLevel::RISCV_RVV>(
|
||||
size_t d,
|
||||
const float* a,
|
||||
const float* b,
|
||||
float* c) {
|
||||
size_t i = 0;
|
||||
while (i < d) {
|
||||
size_t vl = __riscv_vsetvl_e32m8(d - i);
|
||||
vfloat32m8_t va = __riscv_vle32_v_f32m8(a + i, vl);
|
||||
vfloat32m8_t vb = __riscv_vle32_v_f32m8(b + i, vl);
|
||||
va = __riscv_vfadd_vv_f32m8(va, vb, vl);
|
||||
__riscv_vse32_v_f32m8(c + i, va, vl);
|
||||
i += vl;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void fvec_add<SIMDLevel::RISCV_RVV>(
|
||||
size_t d,
|
||||
const float* a,
|
||||
float b,
|
||||
float* c) {
|
||||
size_t i = 0;
|
||||
while (i < d) {
|
||||
size_t vl = __riscv_vsetvl_e32m8(d - i);
|
||||
vfloat32m8_t va = __riscv_vle32_v_f32m8(a + i, vl);
|
||||
va = __riscv_vfadd_vf_f32m8(va, b, vl);
|
||||
__riscv_vse32_v_f32m8(c + i, va, vl);
|
||||
i += vl;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void fvec_sub<SIMDLevel::RISCV_RVV>(
|
||||
size_t d,
|
||||
const float* a,
|
||||
const float* b,
|
||||
float* c) {
|
||||
size_t i = 0;
|
||||
while (i < d) {
|
||||
size_t vl = __riscv_vsetvl_e32m8(d - i);
|
||||
vfloat32m8_t va = __riscv_vle32_v_f32m8(a + i, vl);
|
||||
vfloat32m8_t vb = __riscv_vle32_v_f32m8(b + i, vl);
|
||||
va = __riscv_vfsub_vv_f32m8(va, vb, vl);
|
||||
__riscv_vse32_v_f32m8(c + i, va, vl);
|
||||
i += vl;
|
||||
}
|
||||
}
|
||||
|
||||
#define DEFINE_VECTOR_DISTANCE_RVV_FALLBACK(metric) \
|
||||
template <> \
|
||||
float VectorDistance<metric, SIMDLevel::RISCV_RVV>::operator()( \
|
||||
|
||||
Reference in New Issue
Block a user