faiss 0.6.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/lib/faiss/version.rb +1 -1
  4. data/vendor/faiss/faiss/Index.h +1 -1
  5. data/vendor/faiss/faiss/IndexBinaryHNSW.cpp +6 -7
  6. data/vendor/faiss/faiss/IndexBinaryIVF.cpp +3 -3
  7. data/vendor/faiss/faiss/IndexHNSW.cpp +173 -143
  8. data/vendor/faiss/faiss/IndexIVF.cpp +2 -2
  9. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizer.cpp +2 -2
  10. data/vendor/faiss/faiss/IndexIVFFlat.cpp +3 -1
  11. data/vendor/faiss/faiss/IndexIVFFlatPanorama.cpp +3 -3
  12. data/vendor/faiss/faiss/IndexIVFPQ.cpp +2 -3
  13. data/vendor/faiss/faiss/IndexIVFPQR.cpp +2 -3
  14. data/vendor/faiss/faiss/IndexIVFRaBitQ.cpp +4 -13
  15. data/vendor/faiss/faiss/IndexNNDescent.cpp +1 -1
  16. data/vendor/faiss/faiss/IndexNSG.cpp +1 -2
  17. data/vendor/faiss/faiss/IndexScalarQuantizer.cpp +68 -6
  18. data/vendor/faiss/faiss/IndexScalarQuantizer.h +10 -0
  19. data/vendor/faiss/faiss/cppcontrib/SaDecodeKernels.h +1 -1
  20. data/vendor/faiss/faiss/cppcontrib/sa_decode/Level2-neon-inl.h +902 -12
  21. data/vendor/faiss/faiss/cppcontrib/sa_decode/PQ-neon-inl.h +702 -10
  22. data/vendor/faiss/faiss/factory_tools.cpp +4 -0
  23. data/vendor/faiss/faiss/gpu/GpuResources.h +3 -2
  24. data/vendor/faiss/faiss/gpu/StandardGpuResources.cpp +11 -12
  25. data/vendor/faiss/faiss/gpu/StandardGpuResources.h +3 -3
  26. data/vendor/faiss/faiss/gpu_metal/MetalDistance.h +87 -0
  27. data/vendor/faiss/faiss/gpu_metal/MetalIndex.h +7 -0
  28. data/vendor/faiss/faiss/gpu_metal/MetalIndexIVFFlat.h +181 -0
  29. data/vendor/faiss/faiss/gpu_metal/MetalKernels.h +48 -3
  30. data/vendor/faiss/faiss/gpu_metal/MetalPythonBridge.h +45 -0
  31. data/vendor/faiss/faiss/gpu_metal/impl/MetalIVFFlat.h +193 -0
  32. data/vendor/faiss/faiss/impl/HNSW.cpp +556 -199
  33. data/vendor/faiss/faiss/impl/HNSW.h +51 -13
  34. data/vendor/faiss/faiss/impl/NSG.cpp +15 -11
  35. data/vendor/faiss/faiss/impl/Panorama.h +11 -0
  36. data/vendor/faiss/faiss/impl/ProductQuantizer.cpp +25 -2
  37. data/vendor/faiss/faiss/impl/RaBitQUtils.cpp +1 -1
  38. data/vendor/faiss/faiss/impl/RaBitQuantizer.cpp +7 -1
  39. data/vendor/faiss/faiss/impl/ResultHandler.h +1 -0
  40. data/vendor/faiss/faiss/impl/ScalarQuantizer.cpp +271 -8
  41. data/vendor/faiss/faiss/impl/ScalarQuantizer.h +50 -0
  42. data/vendor/faiss/faiss/impl/VisitedTable.cpp +10 -10
  43. data/vendor/faiss/faiss/impl/VisitedTable.h +69 -34
  44. data/vendor/faiss/faiss/impl/fast_scan/dispatching.h +3 -1
  45. data/vendor/faiss/faiss/impl/hnsw/MinimaxHeap.cpp +35 -43
  46. data/vendor/faiss/faiss/impl/hnsw/MinimaxHeap.h +64 -15
  47. data/vendor/faiss/faiss/impl/hnsw/avx2.cpp +86 -40
  48. data/vendor/faiss/faiss/impl/hnsw/avx512.cpp +81 -50
  49. data/vendor/faiss/faiss/impl/index_read.cpp +100 -39
  50. data/vendor/faiss/faiss/impl/index_write.cpp +1 -0
  51. data/vendor/faiss/faiss/impl/io_macros.h +25 -0
  52. data/vendor/faiss/faiss/impl/platform_macros.h +12 -8
  53. data/vendor/faiss/faiss/impl/pq_code_distance/avx2.cpp +2 -0
  54. data/vendor/faiss/faiss/impl/pq_code_distance/avx512.cpp +2 -0
  55. data/vendor/faiss/faiss/impl/pq_code_distance/neon.cpp +2 -0
  56. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-generic.cpp +20 -0
  57. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-inl.h +36 -0
  58. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-sve.cpp +5 -0
  59. data/vendor/faiss/faiss/impl/pq_code_distance/pq_scan_impl.h +105 -0
  60. data/vendor/faiss/faiss/impl/pq_code_distance/rvv.cpp +2 -0
  61. data/vendor/faiss/faiss/impl/scalar_quantizer/distance_computers.h +6 -0
  62. data/vendor/faiss/faiss/impl/scalar_quantizer/quantizers.h +327 -18
  63. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx2.cpp +264 -27
  64. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-impl.h +553 -0
  65. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-spr.cpp +559 -0
  66. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512.cpp +199 -27
  67. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-dispatch.h +366 -3
  68. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-neon.cpp +144 -19
  69. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-rvv.cpp +26 -0
  70. data/vendor/faiss/faiss/impl/simd_dispatch.h +65 -8
  71. data/vendor/faiss/faiss/index_factory.cpp +5 -1
  72. data/vendor/faiss/faiss/index_io.h +16 -0
  73. data/vendor/faiss/faiss/invlists/DirectMap.cpp +4 -1
  74. data/vendor/faiss/faiss/invlists/InvertedLists.cpp +13 -13
  75. data/vendor/faiss/faiss/invlists/InvertedLists.h +2 -2
  76. data/vendor/faiss/faiss/svs/IndexSVSVamana.cpp +119 -22
  77. data/vendor/faiss/faiss/svs/IndexSVSVamana.h +15 -5
  78. data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.cpp +3 -2
  79. data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.h +2 -1
  80. data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.cpp +65 -24
  81. data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.h +3 -2
  82. data/vendor/faiss/faiss/utils/bf16.h +34 -0
  83. data/vendor/faiss/faiss/utils/distances_simd.cpp +0 -1
  84. data/vendor/faiss/faiss/utils/hamming.cpp +8 -8
  85. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx2.cpp +2 -1
  86. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512_spr.cpp +15 -0
  87. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx512.h +6 -30
  88. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx512_spr.h +171 -0
  89. data/vendor/faiss/faiss/utils/partitioning.cpp +0 -2
  90. data/vendor/faiss/faiss/utils/simd_impl/partitioning_simdlib256.h +14 -68
  91. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512_spr.cpp +343 -0
  92. data/vendor/faiss/faiss/utils/simd_levels.cpp +12 -2
  93. metadata +12 -2
@@ -302,6 +302,32 @@ struct DCTemplate<
302
302
  }
303
303
  };
304
304
 
305
+ /**********************************************************
306
+ * TurboQuant masked_sum RVV specialization (scalar fallback)
307
+ **********************************************************/
308
+
309
+ template <SIMDLevel SL0>
310
+ float turboq_masked_sum(const float* arr, const uint8_t* bits, size_t d);
311
+
312
+ template <>
313
+ float turboq_masked_sum<SIMDLevel::RISCV_RVV>(
314
+ const float* arr,
315
+ const uint8_t* bits,
316
+ size_t d) {
317
+ float result = 0;
318
+ for (size_t byte_idx = 0; byte_idx < (d + 7) / 8; byte_idx++) {
319
+ uint8_t b = bits[byte_idx];
320
+ size_t base = byte_idx * 8;
321
+ size_t end = std::min(base + 8, d);
322
+ for (size_t j = base; j < end; j++) {
323
+ if (b & (1 << (j - base))) {
324
+ result += arr[j];
325
+ }
326
+ }
327
+ }
328
+ return result;
329
+ }
330
+
305
331
  } // namespace scalar_quantizer
306
332
  } // namespace faiss
307
333
 
@@ -36,6 +36,12 @@ constexpr int AVAILABLE_SIMD_LEVELS_AVX2_NEON = AVAILABLE_SIMD_LEVELS_NONE |
36
36
  constexpr int AVAILABLE_SIMD_LEVELS_A0 = AVAILABLE_SIMD_LEVELS_AVX2_NEON |
37
37
  (1 << int(SIMDLevel::AVX512)) | (1 << int(SIMDLevel::RISCV_RVV));
38
38
 
39
+ // A0_SPR: same as A0 + AVX512_SPR (for functions with a dedicated SPR
40
+ // specialization on top of an AVX512 fallback). Currently used by the
41
+ // RaBitQ popcount kernels, which use VPOPCNTDQ on SPR+.
42
+ constexpr int AVAILABLE_SIMD_LEVELS_A0_SPR =
43
+ AVAILABLE_SIMD_LEVELS_A0 | (1 << int(SIMDLevel::AVX512_SPR));
44
+
39
45
  // A1: same + ARM_SVE (for functions with dedicated SVE implementations)
40
46
  constexpr int AVAILABLE_SIMD_LEVELS_A1 =
41
47
  AVAILABLE_SIMD_LEVELS_A0 | (1 << int(SIMDLevel::ARM_SVE));
@@ -47,6 +53,37 @@ constexpr int AVAILABLE_SIMD_LEVELS_A2 = AVAILABLE_SIMD_LEVELS_NONE |
47
53
 
48
54
  constexpr int AVAILABLE_SIMD_LEVELS_ALL = -1;
49
55
 
56
+ constexpr SIMDLevel get_simd_fallback(SIMDLevel level) {
57
+ switch (level) {
58
+ case SIMDLevel::AVX512_SPR:
59
+ return SIMDLevel::AVX512;
60
+ case SIMDLevel::AVX512:
61
+ return SIMDLevel::AVX2;
62
+ case SIMDLevel::ARM_SVE:
63
+ return SIMDLevel::ARM_NEON;
64
+ case SIMDLevel::AVX2:
65
+ case SIMDLevel::ARM_NEON:
66
+ case SIMDLevel::RISCV_RVV:
67
+ return SIMDLevel::NONE;
68
+ default:
69
+ return SIMDLevel::NONE;
70
+ }
71
+ }
72
+
73
+ template <int available_levels, SIMDLevel current_level, typename LambdaType>
74
+ inline auto dispatch_with_fallback(LambdaType&& action) {
75
+ if constexpr (available_levels & (1 << int(current_level))) {
76
+ return action.template operator()<current_level>();
77
+ } else if constexpr (current_level != SIMDLevel::NONE) {
78
+ return dispatch_with_fallback<
79
+ available_levels,
80
+ get_simd_fallback(current_level)>(
81
+ std::forward<LambdaType>(action));
82
+ } else {
83
+ return action.template operator()<SIMDLevel::NONE>();
84
+ }
85
+ }
86
+
50
87
  /** The complete dispatching function. It takes into account:
51
88
  * - the currently selected SIMD level
52
89
  * - the compiled in SIMD levels (given by COMPILE_SIMD_XXX)
@@ -114,14 +151,15 @@ inline auto with_selected_simd_levels(LambdaType&& action) {
114
151
  }
115
152
  #else // static dispatch
116
153
  // In static mode, SINGLE_SIMD_LEVEL is a constexpr resolved at compile
117
- // time. If the compiled level is not in the available set, fall through
118
- // to NONE (mirroring the DD fallthrough behavior). Only SINGLE_SIMD_LEVEL
119
- // and NONE have compiled specializations.
120
- if constexpr (available_levels & (1 << int(SINGLE_SIMD_LEVEL))) {
121
- return action.template operator()<SINGLE_SIMD_LEVEL>();
122
- } else {
123
- return action.template operator()<SIMDLevel::NONE>();
124
- }
154
+ // time. We mirror the DD fallthrough behavior at compile time via
155
+ // dispatch_with_fallback, which recursively walks get_simd_fallback:
156
+ // x86: AVX512_SPR -> AVX512 -> AVX2 -> NONE
157
+ // ARM: ARM_SVE -> ARM_NEON -> NONE
158
+ // RISCV: RISCV_RVV -> NONE
159
+ // The first level in the chain that appears in available_levels is
160
+ // selected; if none match, NONE is used unconditionally.
161
+ return dispatch_with_fallback<available_levels, SINGLE_SIMD_LEVEL>(
162
+ std::forward<LambdaType>(action));
125
163
  #endif
126
164
  }
127
165
 
@@ -160,6 +198,15 @@ inline auto with_simd_level(LambdaType&& action) {
160
198
  std::forward<LambdaType>(action));
161
199
  }
162
200
 
201
+ /**
202
+ * Use for functions with AVX512_SPR-specific implementations.
203
+ */
204
+ template <typename LambdaType>
205
+ inline auto with_simd_level_spr(LambdaType&& action) {
206
+ return with_selected_simd_levels<AVAILABLE_SIMD_LEVELS_A0_SPR>(
207
+ std::forward<LambdaType>(action));
208
+ }
209
+
163
210
  /**
164
211
  * Use for functions implemented with simdXintY (256-bit) operations
165
212
  * that don't have dedicated AVX512 or SVE implementations.
@@ -170,4 +217,14 @@ inline auto with_simd_level_256bit(LambdaType&& action) {
170
217
  std::forward<LambdaType>(action));
171
218
  }
172
219
 
220
+ /**
221
+ * Use for functions that have A0-level implementations plus an AVX512_SPR
222
+ * specialization (e.g. using VPOPCNTDQ).
223
+ */
224
+ template <typename LambdaType>
225
+ inline auto with_simd_level_a0_spr(LambdaType&& action) {
226
+ return with_selected_simd_levels<AVAILABLE_SIMD_LEVELS_A0_SPR>(
227
+ std::forward<LambdaType>(action));
228
+ }
229
+
173
230
  } // namespace faiss
@@ -168,9 +168,13 @@ std::map<std::string, ScalarQuantizer::QuantizerType> sq_types = {
168
168
  {"SQtqmse3", ScalarQuantizer::QT_3bit_tqmse},
169
169
  {"SQtqmse4", ScalarQuantizer::QT_4bit_tqmse},
170
170
  {"SQtqmse8", ScalarQuantizer::QT_8bit_tqmse},
171
+ {"SQtq2", ScalarQuantizer::QT_2bit_tq},
172
+ {"SQtq3", ScalarQuantizer::QT_3bit_tq},
173
+ {"SQtq4", ScalarQuantizer::QT_4bit_tq},
174
+ {"SQtq5", ScalarQuantizer::QT_5bit_tq},
171
175
  };
172
176
  const std::string sq_pattern =
173
- "(SQ0|SQ4|SQ8|SQ6|SQfp16|SQbf16|SQ8_direct_signed|SQ8_direct|SQtqmse1|SQtqmse2|SQtqmse3|SQtqmse4|SQtqmse8)";
177
+ "(SQ0|SQ4|SQ8|SQ6|SQfp16|SQbf16|SQ8_direct_signed|SQ8_direct|SQtqmse1|SQtqmse2|SQtqmse3|SQtqmse4|SQtqmse8|SQtq2|SQtq3|SQtq4|SQtq5)";
174
178
 
175
179
  std::map<std::string, AdditiveQuantizer::Search_type_t> aq_search_type = {
176
180
  {"_Nfloat", AdditiveQuantizer::ST_norm_float},
@@ -137,6 +137,22 @@ size_t get_deserialization_vector_byte_limit();
137
137
  // and do not modify while deserialization is in progress on other threads.
138
138
  void set_deserialization_vector_byte_limit(size_t value);
139
139
 
140
+ // Returns the current IndexLattice r2 limit for deserialization.
141
+ // When nonzero, deserialization rejects IndexLattice payloads whose
142
+ // r2 (squared lattice radius) exceeds this value. The
143
+ // ZnSphereCodecRec constructor that runs at IndexLattice deserialize
144
+ // time builds a decode cache whose population cost scales
145
+ // polynomially in r2 and dim, and can exceed real-world workload time
146
+ // budgets even for r2 values that do not trip the existing
147
+ // decode-cache memory cap.
148
+ // Default: 0 (no limit).
149
+ size_t get_deserialization_lattice_r2_limit();
150
+
151
+ // Sets the IndexLattice r2 deserialization limit.
152
+ // NOT thread-safe: set before any concurrent deserialization calls
153
+ // and do not modify while deserialization is in progress on other threads.
154
+ void set_deserialization_lattice_r2_limit(size_t value);
155
+
140
156
  } // namespace faiss
141
157
 
142
158
  #endif
@@ -254,7 +254,10 @@ void DirectMap::update_codes(
254
254
  int64_t id2 = invlists->get_single_id(il, l - 1);
255
255
  array[id2] = lo_build(il, ofs);
256
256
  invlists->update_entry(
257
- il, ofs, id2, invlists->get_single_code(il, l - 1));
257
+ il,
258
+ ofs,
259
+ id2,
260
+ InvertedLists::ScopedCodes(invlists, il, l - 1).get());
258
261
  }
259
262
  invlists->resize(il, l - 1);
260
263
  }
@@ -498,12 +498,12 @@ void ReadOnlyInvertedLists::resize(size_t, size_t) {
498
498
  * HStackInvertedLists implementation
499
499
  ******************************************/
500
500
 
501
- HStackInvertedLists::HStackInvertedLists(int nil, const InvertedLists** ils_in)
501
+ HStackInvertedLists::HStackInvertedLists(int n_il, const InvertedLists** ils_in)
502
502
  : ReadOnlyInvertedLists(
503
- nil > 0 ? ils_in[0]->nlist : 0,
504
- nil > 0 ? ils_in[0]->code_size : 0) {
505
- FAISS_THROW_IF_NOT(nil > 0);
506
- for (int i = 0; i < nil; i++) {
503
+ n_il > 0 ? ils_in[0]->nlist : 0,
504
+ n_il > 0 ? ils_in[0]->code_size : 0) {
505
+ FAISS_THROW_IF_NOT(n_il > 0);
506
+ for (int i = 0; i < n_il; i++) {
507
507
  ils.push_back(ils_in[i]);
508
508
  FAISS_THROW_IF_NOT(
509
509
  ils_in[i]->code_size == code_size && ils_in[i]->nlist == nlist);
@@ -683,9 +683,9 @@ int translate_list_no(const VStackInvertedLists* vil, idx_t list_no) {
683
683
  return i0;
684
684
  }
685
685
 
686
- idx_t sum_il_sizes(int nil, const InvertedLists** ils_in) {
686
+ idx_t sum_il_sizes(int n_il, const InvertedLists** ils_in) {
687
687
  idx_t tot = 0;
688
- for (int i = 0; i < nil; i++) {
688
+ for (int i = 0; i < n_il; i++) {
689
689
  tot += ils_in[i]->nlist;
690
690
  }
691
691
  return tot;
@@ -693,13 +693,13 @@ idx_t sum_il_sizes(int nil, const InvertedLists** ils_in) {
693
693
 
694
694
  } // namespace
695
695
 
696
- VStackInvertedLists::VStackInvertedLists(int nil, const InvertedLists** ils_in)
696
+ VStackInvertedLists::VStackInvertedLists(int n_il, const InvertedLists** ils_in)
697
697
  : ReadOnlyInvertedLists(
698
- sum_il_sizes(nil, ils_in),
699
- nil > 0 ? ils_in[0]->code_size : 0) {
700
- FAISS_THROW_IF_NOT(nil > 0);
701
- cumsz.resize(nil + 1);
702
- for (int i = 0; i < nil; i++) {
698
+ sum_il_sizes(n_il, ils_in),
699
+ n_il > 0 ? ils_in[0]->code_size : 0) {
700
+ FAISS_THROW_IF_NOT(n_il > 0);
701
+ cumsz.resize(n_il + 1);
702
+ for (int i = 0; i < n_il; i++) {
703
703
  ils.push_back(ils_in[i]);
704
704
  FAISS_THROW_IF_NOT(ils_in[i]->code_size == code_size);
705
705
  cumsz[i + 1] = cumsz[i] + ils_in[i]->nlist;
@@ -376,7 +376,7 @@ struct HStackInvertedLists : ReadOnlyInvertedLists {
376
376
  std::vector<const InvertedLists*> ils;
377
377
 
378
378
  /// build InvertedLists by concatenating nil of them
379
- HStackInvertedLists(int nil, const InvertedLists** ils);
379
+ HStackInvertedLists(int n_il, const InvertedLists** ils);
380
380
 
381
381
  size_t list_size(size_t list_no) const override;
382
382
  const uint8_t* get_codes(size_t list_no) const override;
@@ -422,7 +422,7 @@ struct VStackInvertedLists : ReadOnlyInvertedLists {
422
422
  std::vector<idx_t> cumsz;
423
423
 
424
424
  /// build InvertedLists by concatenating nil of them
425
- VStackInvertedLists(int nil, const InvertedLists** ils);
425
+ VStackInvertedLists(int n_il, const InvertedLists** ils);
426
426
 
427
427
  size_t list_size(size_t list_no) const override;
428
428
  const uint8_t* get_codes(size_t list_no) const override;
@@ -65,8 +65,12 @@ IndexSVSVamana::IndexSVSVamana(
65
65
  idx_t d,
66
66
  size_t degree,
67
67
  MetricType metric,
68
- SVSStorageKind storage)
69
- : Index(d, metric), graph_max_degree{degree}, storage_kind{storage} {
68
+ SVSStorageKind storage,
69
+ bool is_static)
70
+ : Index(d, metric),
71
+ graph_max_degree{degree},
72
+ is_static{is_static},
73
+ storage_kind{storage} {
70
74
  prune_to = graph_max_degree < 4 ? graph_max_degree : graph_max_degree - 4;
71
75
  alpha = metric == METRIC_L2 ? 1.2f : 0.95f;
72
76
 
@@ -74,8 +78,9 @@ IndexSVSVamana::IndexSVSVamana(
74
78
  // NB: LVQ/LeanVec are only available on Intel(R) hardware AND when using
75
79
  // a build based on LVQ/LeanVec-enabled SVS.
76
80
  auto svs_storage = to_svs_storage_kind(storage_kind);
77
- auto status =
78
- svs_runtime::DynamicVamanaIndex::check_storage_kind(svs_storage);
81
+ auto status = is_static
82
+ ? svs_runtime::VamanaIndex::check_storage_kind(svs_storage)
83
+ : svs_runtime::DynamicVamanaIndex::check_storage_kind(svs_storage);
79
84
  if (!status.ok()) {
80
85
  FAISS_THROW_MSG(status.message());
81
86
  }
@@ -83,11 +88,19 @@ IndexSVSVamana::IndexSVSVamana(
83
88
 
84
89
  bool IndexSVSVamana::is_lvq_leanvec_enabled() {
85
90
  auto lvq = to_svs_storage_kind(SVS_LVQ4x0);
86
- auto status = svs_runtime::DynamicVamanaIndex::check_storage_kind(lvq);
91
+ auto status = svs_runtime::VamanaIndex::check_storage_kind(lvq);
92
+ if (!status.ok()) {
93
+ return false;
94
+ }
95
+ status = svs_runtime::DynamicVamanaIndex::check_storage_kind(lvq);
87
96
  if (!status.ok()) {
88
97
  return false;
89
98
  }
90
99
  auto leanvec = to_svs_storage_kind(SVS_LeanVec4x4);
100
+ status = svs_runtime::VamanaIndex::check_storage_kind(leanvec);
101
+ if (!status.ok()) {
102
+ return false;
103
+ }
91
104
  status = svs_runtime::DynamicVamanaIndex::check_storage_kind(leanvec);
92
105
  if (!status.ok()) {
93
106
  return false;
@@ -97,21 +110,43 @@ bool IndexSVSVamana::is_lvq_leanvec_enabled() {
97
110
 
98
111
  IndexSVSVamana::~IndexSVSVamana() {
99
112
  if (impl) {
100
- auto status = svs_runtime::DynamicVamanaIndex::destroy(impl);
113
+ svs_runtime::Status status;
114
+ if (is_static) {
115
+ status = svs_runtime::VamanaIndex::destroy(impl);
116
+ } else {
117
+ status = svs_runtime::DynamicVamanaIndex::destroy(
118
+ static_cast<svs_runtime::DynamicVamanaIndex*>(impl));
119
+ }
101
120
  FAISS_ASSERT(status.ok());
102
121
  impl = nullptr;
103
122
  }
104
123
  }
105
124
 
106
125
  void IndexSVSVamana::add(idx_t n, const float* x) {
126
+ if (is_static) {
127
+ FAISS_THROW_IF_MSG(
128
+ impl,
129
+ "Static Vamana index does not support add() after initial "
130
+ "build. All data must be provided in a single add() call.");
131
+ create_impl(n, x);
132
+ if (stored_vectors_valid) {
133
+ stored_vectors.resize(static_cast<size_t>(n) * d);
134
+ std::memcpy(stored_vectors.data(), x, sizeof(float) * n * d);
135
+ }
136
+ ntotal = n;
137
+ is_trained = true;
138
+ return;
139
+ }
140
+
107
141
  if (!impl) {
108
- create_impl();
142
+ create_impl(0, nullptr);
109
143
  }
110
144
 
111
145
  std::vector<size_t> labels(n);
112
146
  std::iota(labels.begin(), labels.end(), ntotal);
113
147
 
114
- auto status = impl->add(n, labels.data(), x);
148
+ auto* dyn = dynamic_impl();
149
+ auto status = dyn->add(n, labels.data(), x);
115
150
  if (!status.ok()) {
116
151
  FAISS_THROW_MSG(status.message());
117
152
  }
@@ -137,7 +172,18 @@ void IndexSVSVamana::reconstruct(idx_t key, float* recons) const {
137
172
 
138
173
  void IndexSVSVamana::reset() {
139
174
  if (impl) {
140
- impl->reset();
175
+ if (is_static) {
176
+ // Static index: destroy the impl so the next add() rebuilds from
177
+ // scratch. The static SVS backend has no in-place reset that
178
+ // permits a follow-up add().
179
+ auto status = svs_runtime::VamanaIndex::destroy(impl);
180
+ FAISS_ASSERT(status.ok());
181
+ impl = nullptr;
182
+ } else {
183
+ // Dynamic index: in-place reset preserves the impl and avoids
184
+ // tearing down its allocated state.
185
+ impl->reset();
186
+ }
141
187
  }
142
188
  stored_vectors.clear();
143
189
  stored_vectors_valid = true;
@@ -204,17 +250,22 @@ void IndexSVSVamana::range_search(
204
250
  }
205
251
 
206
252
  size_t IndexSVSVamana::remove_ids(const IDSelector& sel) {
253
+ FAISS_THROW_IF_MSG(
254
+ is_static,
255
+ "Static Vamana index does not support remove_ids(). "
256
+ "The index is immutable after creation.");
207
257
  FAISS_THROW_IF_NOT(impl);
258
+ auto* dyn = dynamic_impl();
208
259
  auto id_filter = FaissIDFilter{sel};
209
260
  size_t removed = 0;
210
- auto Status = impl->remove_selected(&removed, id_filter);
261
+ auto Status = dyn->remove_selected(&removed, id_filter);
211
262
  ntotal -= removed;
212
263
  stored_vectors.clear();
213
264
  stored_vectors_valid = false;
214
265
  return removed;
215
266
  }
216
267
 
217
- void IndexSVSVamana::create_impl() {
268
+ void IndexSVSVamana::create_impl(idx_t n, const float* x) {
218
269
  FAISS_THROW_IF_NOT(!impl);
219
270
  ntotal = 0;
220
271
  auto svs_metric = to_svs_metric(metric_type);
@@ -231,15 +282,45 @@ void IndexSVSVamana::create_impl() {
231
282
  .search_window_size = search_window_size,
232
283
  .search_buffer_capacity = search_buffer_capacity,
233
284
  };
234
- auto Status = svs_runtime::DynamicVamanaIndex::build(
235
- &impl,
236
- d,
237
- svs_metric,
238
- svs_storage_kind,
239
- build_params,
240
- search_params);
241
- if (!Status.ok()) {
242
- FAISS_THROW_MSG(Status.message());
285
+
286
+ svs_runtime::Status Status;
287
+ if (is_static) {
288
+ FAISS_THROW_IF_NOT_MSG(
289
+ n > 0 && x != nullptr,
290
+ "Static Vamana index requires data at build time.");
291
+ Status = svs_runtime::VamanaIndex::build(
292
+ &impl,
293
+ d,
294
+ svs_metric,
295
+ svs_storage_kind,
296
+ build_params,
297
+ search_params);
298
+ if (!Status.ok()) {
299
+ FAISS_THROW_MSG(Status.message());
300
+ }
301
+ FAISS_THROW_IF_NOT(impl);
302
+ // Populate the static index with the full dataset (one-shot add).
303
+ Status = impl->add(static_cast<size_t>(n), x);
304
+ if (!Status.ok()) {
305
+ // Best-effort cleanup before propagating the error.
306
+ auto destroy_status = svs_runtime::VamanaIndex::destroy(impl);
307
+ FAISS_ASSERT(destroy_status.ok());
308
+ impl = nullptr;
309
+ FAISS_THROW_MSG(Status.message());
310
+ }
311
+ } else {
312
+ svs_runtime::DynamicVamanaIndex* dyn_impl = nullptr;
313
+ Status = svs_runtime::DynamicVamanaIndex::build(
314
+ &dyn_impl,
315
+ d,
316
+ svs_metric,
317
+ svs_storage_kind,
318
+ build_params,
319
+ search_params);
320
+ if (!Status.ok()) {
321
+ FAISS_THROW_MSG(Status.message());
322
+ }
323
+ impl = dyn_impl;
243
324
  }
244
325
  FAISS_THROW_IF_NOT(impl);
245
326
  }
@@ -258,12 +339,28 @@ void IndexSVSVamana::deserialize_impl(std::istream& in) {
258
339
  FAISS_THROW_IF_MSG(impl, "Cannot deserialize: SVS index already loaded.");
259
340
  auto svs_metric = to_svs_metric(metric_type);
260
341
  auto svs_storage_kind = to_svs_storage_kind(storage_kind);
261
- auto status = svs_runtime::DynamicVamanaIndex::load(
262
- &impl, in, svs_metric, svs_storage_kind);
342
+
343
+ svs_runtime::Status status;
344
+ if (is_static) {
345
+ status = svs_runtime::VamanaIndex::load(
346
+ &impl, in, svs_metric, svs_storage_kind);
347
+ } else {
348
+ svs_runtime::DynamicVamanaIndex* dyn_impl = nullptr;
349
+ status = svs_runtime::DynamicVamanaIndex::load(
350
+ &dyn_impl, in, svs_metric, svs_storage_kind);
351
+ impl = dyn_impl;
352
+ }
263
353
  if (!status.ok()) {
264
354
  FAISS_THROW_MSG(status.message());
265
355
  }
266
356
  FAISS_THROW_IF_NOT_MSG(impl, "Failed to load SVS Vamana index.");
267
357
  }
268
358
 
359
+ svs_runtime::DynamicVamanaIndex* IndexSVSVamana::dynamic_impl() const {
360
+ FAISS_THROW_IF_MSG(
361
+ is_static, "Operation not supported on a static Vamana index.");
362
+ FAISS_THROW_IF_NOT(impl);
363
+ return static_cast<svs_runtime::DynamicVamanaIndex*>(impl);
364
+ }
365
+
269
366
  } // namespace faiss
@@ -94,6 +94,9 @@ struct IndexSVSVamana : Index {
94
94
  size_t max_candidate_pool_size = 200;
95
95
  bool use_full_search_history = true;
96
96
 
97
+ /// Whether this is a static (immutable) Vamana index
98
+ bool is_static = false;
99
+
97
100
  SVSStorageKind storage_kind = SVS_FP32;
98
101
 
99
102
  IndexSVSVamana();
@@ -102,7 +105,8 @@ struct IndexSVSVamana : Index {
102
105
  idx_t d,
103
106
  size_t degree,
104
107
  MetricType metric = METRIC_L2,
105
- SVSStorageKind storage = SVSStorageKind::SVS_FP32);
108
+ SVSStorageKind storage = SVSStorageKind::SVS_FP32,
109
+ bool is_static = false);
106
110
 
107
111
  ~IndexSVSVamana() override;
108
112
 
@@ -137,8 +141,9 @@ struct IndexSVSVamana : Index {
137
141
  void serialize_impl(std::ostream& out) const;
138
142
  virtual void deserialize_impl(std::istream& in);
139
143
 
140
- /* The actual SVS implementation */
141
- svs_runtime::DynamicVamanaIndex* impl{nullptr};
144
+ /* The actual SVS implementation (VamanaIndex is the base for both
145
+ static and dynamic variants) */
146
+ svs_runtime::VamanaIndex* impl{nullptr};
142
147
 
143
148
  // The SVS runtime API does not expose vector retrieval, so we keep a copy
144
149
  // of added vectors to support reconstruct(). When used as a coarse
@@ -147,8 +152,13 @@ struct IndexSVSVamana : Index {
147
152
  bool stored_vectors_valid{true};
148
153
 
149
154
  protected:
150
- /* Initializes the implementation*/
151
- virtual void create_impl();
155
+ /* Initializes the implementation. For static indexes the data is consumed
156
+ at build time; for dynamic indexes n/x are ignored and add() populates
157
+ the index afterwards. */
158
+ virtual void create_impl(idx_t n, const float* x);
159
+
160
+ /* Returns the dynamic impl pointer, throwing if static */
161
+ svs_runtime::DynamicVamanaIndex* dynamic_impl() const;
152
162
  };
153
163
 
154
164
  } // namespace faiss
@@ -33,7 +33,8 @@ IndexSVSVamanaLVQ::IndexSVSVamanaLVQ(
33
33
  idx_t d,
34
34
  size_t degree,
35
35
  MetricType metric,
36
- SVSStorageKind storage)
37
- : IndexSVSVamana(d, degree, metric, storage) {}
36
+ SVSStorageKind storage,
37
+ bool is_static)
38
+ : IndexSVSVamana(d, degree, metric, storage, is_static) {}
38
39
 
39
40
  } // namespace faiss
@@ -34,7 +34,8 @@ struct IndexSVSVamanaLVQ : IndexSVSVamana {
34
34
  idx_t d,
35
35
  size_t degree,
36
36
  MetricType metric = METRIC_L2,
37
- SVSStorageKind storage = SVSStorageKind::SVS_LVQ4x0);
37
+ SVSStorageKind storage = SVSStorageKind::SVS_LVQ4x0,
38
+ bool is_static = false);
38
39
 
39
40
  ~IndexSVSVamanaLVQ() override = default;
40
41
  };
@@ -44,8 +44,9 @@ IndexSVSVamanaLeanVec::IndexSVSVamanaLeanVec(
44
44
  size_t degree,
45
45
  MetricType metric,
46
46
  size_t leanvec_dims,
47
- SVSStorageKind storage_kind)
48
- : IndexSVSVamana(d, degree, metric, storage_kind) {
47
+ SVSStorageKind storage_kind,
48
+ bool is_static)
49
+ : IndexSVSVamana(d, degree, metric, storage_kind, is_static) {
49
50
  is_trained = false;
50
51
  leanvec_d = leanvec_dims == 0 ? d / 2 : leanvec_dims;
51
52
  }
@@ -120,7 +121,8 @@ void IndexSVSVamanaLeanVec::deserialize_training_data(std::istream& in) {
120
121
  training_data = tdata;
121
122
  }
122
123
 
123
- void IndexSVSVamanaLeanVec::create_impl() {
124
+ void IndexSVSVamanaLeanVec::create_impl(idx_t n, const float* x) {
125
+ FAISS_THROW_IF_NOT(!impl);
124
126
  ntotal = 0;
125
127
  auto svs_metric = to_svs_metric(metric_type);
126
128
  auto svs_storage_kind = to_svs_storage_kind(storage_kind);
@@ -136,29 +138,68 @@ void IndexSVSVamanaLeanVec::create_impl() {
136
138
  .search_window_size = search_window_size,
137
139
  .search_buffer_capacity = search_buffer_capacity,
138
140
  };
141
+
139
142
  auto status = svs_runtime::Status_Ok;
140
- if (training_data) {
141
- status = svs_runtime::DynamicVamanaIndexLeanVec::build(
142
- &impl,
143
- d,
144
- svs_metric,
145
- svs_storage_kind,
146
- training_data,
147
- build_params,
148
- search_params);
143
+ if (is_static) {
144
+ FAISS_THROW_IF_NOT_MSG(
145
+ n > 0 && x != nullptr,
146
+ "Static Vamana LeanVec index requires data at build time.");
147
+ if (training_data) {
148
+ status = svs_runtime::VamanaIndexLeanVec::build(
149
+ &impl,
150
+ d,
151
+ svs_metric,
152
+ svs_storage_kind,
153
+ training_data,
154
+ build_params,
155
+ search_params);
156
+ } else {
157
+ status = svs_runtime::VamanaIndexLeanVec::build(
158
+ &impl,
159
+ d,
160
+ svs_metric,
161
+ svs_storage_kind,
162
+ leanvec_d,
163
+ build_params,
164
+ search_params);
165
+ }
166
+ if (!status.ok()) {
167
+ FAISS_THROW_MSG(status.message());
168
+ }
169
+ FAISS_THROW_IF_NOT(impl);
170
+ // Populate the static index with the full dataset (one-shot add).
171
+ status = impl->add(static_cast<size_t>(n), x);
172
+ if (!status.ok()) {
173
+ auto destroy_status = svs_runtime::VamanaIndex::destroy(impl);
174
+ FAISS_ASSERT(destroy_status.ok());
175
+ impl = nullptr;
176
+ FAISS_THROW_MSG(status.message());
177
+ }
149
178
  } else {
150
- status = svs_runtime::DynamicVamanaIndexLeanVec::build(
151
- &impl,
152
- d,
153
- svs_metric,
154
- svs_storage_kind,
155
- leanvec_d,
156
- build_params,
157
- search_params);
158
- }
159
-
160
- if (!status.ok()) {
161
- FAISS_THROW_MSG(status.message());
179
+ svs_runtime::DynamicVamanaIndex* dyn_impl = nullptr;
180
+ if (training_data) {
181
+ status = svs_runtime::DynamicVamanaIndexLeanVec::build(
182
+ &dyn_impl,
183
+ d,
184
+ svs_metric,
185
+ svs_storage_kind,
186
+ training_data,
187
+ build_params,
188
+ search_params);
189
+ } else {
190
+ status = svs_runtime::DynamicVamanaIndexLeanVec::build(
191
+ &dyn_impl,
192
+ d,
193
+ svs_metric,
194
+ svs_storage_kind,
195
+ leanvec_d,
196
+ build_params,
197
+ search_params);
198
+ }
199
+ if (!status.ok()) {
200
+ FAISS_THROW_MSG(status.message());
201
+ }
202
+ impl = dyn_impl;
162
203
  }
163
204
  FAISS_THROW_IF_NOT(impl);
164
205
  }