faiss 0.6.4 → 0.6.5

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 (228) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/ext/faiss/extconf.rb +1 -1
  4. data/lib/faiss/version.rb +1 -1
  5. data/vendor/faiss/faiss/AutoTune.cpp +175 -0
  6. data/vendor/faiss/faiss/Clustering.cpp +150 -60
  7. data/vendor/faiss/faiss/Clustering.h +29 -0
  8. data/vendor/faiss/faiss/IVFlib.cpp +99 -0
  9. data/vendor/faiss/faiss/IVFlib.h +15 -0
  10. data/vendor/faiss/faiss/Index.cpp +44 -0
  11. data/vendor/faiss/faiss/Index.h +19 -14
  12. data/vendor/faiss/faiss/IndexBinary.cpp +8 -0
  13. data/vendor/faiss/faiss/IndexBinary.h +8 -0
  14. data/vendor/faiss/faiss/IndexBinaryHNSW.cpp +82 -4
  15. data/vendor/faiss/faiss/IndexBinaryHNSW.h +19 -0
  16. data/vendor/faiss/faiss/IndexBinaryIVF.cpp +34 -0
  17. data/vendor/faiss/faiss/IndexFastScan.cpp +33 -17
  18. data/vendor/faiss/faiss/IndexFastScan.h +6 -3
  19. data/vendor/faiss/faiss/IndexFlat.cpp +56 -0
  20. data/vendor/faiss/faiss/IndexFlat.h +31 -0
  21. data/vendor/faiss/faiss/IndexFlatCodes.cpp +6 -1
  22. data/vendor/faiss/faiss/IndexHNSW.cpp +456 -10
  23. data/vendor/faiss/faiss/IndexHNSW.h +38 -0
  24. data/vendor/faiss/faiss/IndexIDMap.cpp +32 -0
  25. data/vendor/faiss/faiss/IndexIDMap.h +7 -0
  26. data/vendor/faiss/faiss/IndexIVF.cpp +281 -31
  27. data/vendor/faiss/faiss/IndexIVF.h +53 -1
  28. data/vendor/faiss/faiss/IndexIVFFastScan.cpp +46 -14
  29. data/vendor/faiss/faiss/IndexIVFFastScan.h +4 -2
  30. data/vendor/faiss/faiss/IndexIVFFlat.cpp +33 -0
  31. data/vendor/faiss/faiss/IndexIVFFlat.h +16 -0
  32. data/vendor/faiss/faiss/IndexIVFPQFastScan.cpp +1 -0
  33. data/vendor/faiss/faiss/IndexIVFRaBitQ.cpp +3 -20
  34. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.cpp +50 -14
  35. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.h +5 -2
  36. data/vendor/faiss/faiss/IndexIVFSQFastScan.cpp +855 -0
  37. data/vendor/faiss/faiss/IndexIVFSQFastScan.h +145 -0
  38. data/vendor/faiss/faiss/IndexNNDescent.cpp +0 -3
  39. data/vendor/faiss/faiss/IndexPiPNN.cpp +201 -0
  40. data/vendor/faiss/faiss/IndexPiPNN.h +83 -0
  41. data/vendor/faiss/faiss/IndexRaBitQFastScan.cpp +6 -3
  42. data/vendor/faiss/faiss/IndexRaBitQFastScan.h +10 -4
  43. data/vendor/faiss/faiss/IndexReplicas.cpp +20 -0
  44. data/vendor/faiss/faiss/IndexReplicas.h +9 -0
  45. data/vendor/faiss/faiss/IndexSQFastScan.cpp +648 -0
  46. data/vendor/faiss/faiss/IndexSQFastScan.h +129 -0
  47. data/vendor/faiss/faiss/IndexShards.cpp +10 -0
  48. data/vendor/faiss/faiss/IndexShards.h +5 -0
  49. data/vendor/faiss/faiss/SuperKMeans.cpp +57 -15
  50. data/vendor/faiss/faiss/SuperKMeans.h +9 -4
  51. data/vendor/faiss/faiss/VectorTransform.cpp +3 -2
  52. data/vendor/faiss/faiss/clone_index.cpp +24 -0
  53. data/vendor/faiss/faiss/gpu/Dataset.h +112 -0
  54. data/vendor/faiss/faiss/gpu/GpuCloner.cpp +16 -13
  55. data/vendor/faiss/faiss/gpu/GpuCloner.h +4 -0
  56. data/vendor/faiss/faiss/gpu/GpuClonerOptions.h +3 -0
  57. data/vendor/faiss/faiss/gpu/GpuIndex.h +4 -13
  58. data/vendor/faiss/faiss/gpu/GpuIndexBinaryCagra.h +26 -13
  59. data/vendor/faiss/faiss/gpu/GpuIndexCagra.h +45 -22
  60. data/vendor/faiss/faiss/gpu/GpuIndexFlat.h +11 -0
  61. data/vendor/faiss/faiss/gpu/GpuIndexIVF.h +6 -0
  62. data/vendor/faiss/faiss/gpu/test/TestGpuDatasetHeader.cpp +18 -0
  63. data/vendor/faiss/faiss/gpu/test/TestGpuIndexFlat.cpp +215 -0
  64. data/vendor/faiss/faiss/gpu/test/TestGpuIndexIVFFlat.cpp +98 -0
  65. data/vendor/faiss/faiss/gpu/utils/DeviceUtils.h +11 -0
  66. data/vendor/faiss/faiss/gpu/utils/StackDeviceMemory.cpp +1 -1
  67. data/vendor/faiss/faiss/gpu_metal/MetalCloner.h +11 -0
  68. data/vendor/faiss/faiss/gpu_metal/MetalDistance.h +92 -0
  69. data/vendor/faiss/faiss/gpu_metal/MetalIndex.h +4 -0
  70. data/vendor/faiss/faiss/gpu_metal/MetalIndexBinaryFlat.h +69 -0
  71. data/vendor/faiss/faiss/gpu_metal/MetalIndexIVFPQ.h +38 -0
  72. data/vendor/faiss/faiss/gpu_metal/MetalKernels.h +106 -0
  73. data/vendor/faiss/faiss/gpu_metal/MetalPythonBridge.h +10 -0
  74. data/vendor/faiss/faiss/impl/AuxIndexStructures.cpp +2 -0
  75. data/vendor/faiss/faiss/impl/AuxIndexStructures.h +1 -6
  76. data/vendor/faiss/faiss/impl/ClusteringHelpers.cpp +134 -45
  77. data/vendor/faiss/faiss/impl/ClusteringHelpers.h +36 -0
  78. data/vendor/faiss/faiss/impl/ClusteringInitialization.cpp +286 -136
  79. data/vendor/faiss/faiss/impl/DistanceComputer.cpp +63 -0
  80. data/vendor/faiss/faiss/impl/DistanceComputer.h +3 -29
  81. data/vendor/faiss/faiss/impl/HNSW.cpp +46 -10
  82. data/vendor/faiss/faiss/impl/HNSW.h +72 -0
  83. data/vendor/faiss/faiss/impl/LocalSearchQuantizer.cpp +6 -19
  84. data/vendor/faiss/faiss/impl/NNDescent.cpp +2 -1
  85. data/vendor/faiss/faiss/impl/NSG.cpp +3 -2
  86. data/vendor/faiss/faiss/impl/Panorama.h +3 -2
  87. data/vendor/faiss/faiss/impl/PiPNN.cpp +822 -0
  88. data/vendor/faiss/faiss/impl/PiPNN.h +122 -0
  89. data/vendor/faiss/faiss/impl/ProductQuantizer.cpp +54 -11
  90. data/vendor/faiss/faiss/impl/RaBitQUtils.h +1 -1
  91. data/vendor/faiss/faiss/impl/RaBitQuantizer.cpp +81 -57
  92. data/vendor/faiss/faiss/impl/RaBitQuantizer.h +15 -0
  93. data/vendor/faiss/faiss/impl/ScalarQuantizer.cpp +29 -0
  94. data/vendor/faiss/faiss/impl/ScalarQuantizer.h +1 -6
  95. data/vendor/faiss/faiss/impl/approx_topk/avx2.cpp +0 -4
  96. data/vendor/faiss/faiss/impl/approx_topk/neon.cpp +0 -4
  97. data/vendor/faiss/faiss/impl/binary_hamming/IndexBinaryHash_impl.h +5 -2
  98. data/vendor/faiss/faiss/impl/binary_hamming/avx2.cpp +0 -4
  99. data/vendor/faiss/faiss/impl/binary_hamming/avx512.cpp +0 -4
  100. data/vendor/faiss/faiss/impl/binary_hamming/neon.cpp +0 -4
  101. data/vendor/faiss/faiss/impl/binary_hamming/rvv.cpp +0 -4
  102. data/vendor/faiss/faiss/impl/expanded_scanners.h +88 -0
  103. data/vendor/faiss/faiss/impl/fast_scan/FastScanDistancePostProcessing.h +4 -0
  104. data/vendor/faiss/faiss/impl/fast_scan/accumulate_loops.h +61 -64
  105. data/vendor/faiss/faiss/impl/fast_scan/accumulate_loops_512.h +13 -14
  106. data/vendor/faiss/faiss/impl/fast_scan/decompose_qbs.h +17 -129
  107. data/vendor/faiss/faiss/impl/fast_scan/dispatching.h +25 -19
  108. data/vendor/faiss/faiss/impl/fast_scan/fast_scan.cpp +5 -0
  109. data/vendor/faiss/faiss/impl/fast_scan/impl-avx2.cpp +0 -4
  110. data/vendor/faiss/faiss/impl/fast_scan/impl-avx512.cpp +0 -4
  111. data/vendor/faiss/faiss/impl/fast_scan/impl-neon.cpp +0 -88
  112. data/vendor/faiss/faiss/impl/fast_scan/impl-riscv.cpp +0 -4
  113. data/vendor/faiss/faiss/impl/fast_scan/kernels_neon.h +155 -0
  114. data/vendor/faiss/faiss/impl/fast_scan/kernels_simd256.h +22 -1
  115. data/vendor/faiss/faiss/impl/fast_scan/kernels_simd512.h +3 -2
  116. data/vendor/faiss/faiss/impl/fast_scan/simd_result_handlers.h +41 -23
  117. data/vendor/faiss/faiss/impl/hnsw/avx2.cpp +0 -4
  118. data/vendor/faiss/faiss/impl/hnsw/avx512.cpp +0 -4
  119. data/vendor/faiss/faiss/impl/index_read.cpp +172 -0
  120. data/vendor/faiss/faiss/impl/index_write.cpp +95 -0
  121. data/vendor/faiss/faiss/impl/pipnn/HashPrune.cpp +159 -0
  122. data/vendor/faiss/faiss/impl/pipnn/HashPrune.h +69 -0
  123. data/vendor/faiss/faiss/impl/pipnn/Partition.cpp +587 -0
  124. data/vendor/faiss/faiss/impl/pipnn/Partition.h +93 -0
  125. data/vendor/faiss/faiss/impl/pipnn/kernels.cpp +326 -0
  126. data/vendor/faiss/faiss/impl/pipnn/kernels.h +92 -0
  127. data/vendor/faiss/faiss/impl/platform_macros.h +45 -0
  128. data/vendor/faiss/faiss/impl/polysemous_training/avx512.cpp +0 -4
  129. data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQScanner_impl.h +79 -4
  130. data/vendor/faiss/faiss/impl/pq_code_distance/PQDistanceComputer_impl.h +6 -0
  131. data/vendor/faiss/faiss/impl/pq_code_distance/avx2.cpp +0 -4
  132. data/vendor/faiss/faiss/impl/pq_code_distance/avx512.cpp +0 -4
  133. data/vendor/faiss/faiss/impl/pq_code_distance/neon.cpp +0 -4
  134. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-avx2.h +4 -0
  135. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-avx512.h +4 -1
  136. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-generic.cpp +19 -0
  137. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-generic.h +6 -6
  138. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-inl.h +131 -6
  139. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-sve.cpp +0 -4
  140. data/vendor/faiss/faiss/impl/pq_code_distance/pq_rvv_kernel.h +89 -0
  141. data/vendor/faiss/faiss/impl/pq_code_distance/pq_scan_impl.h +21 -10
  142. data/vendor/faiss/faiss/impl/pq_code_distance/rvv.cpp +45 -18
  143. data/vendor/faiss/faiss/impl/residual_quantizer_encode_steps.cpp +2 -1
  144. data/vendor/faiss/faiss/impl/result_handler/avx2.cpp +0 -4
  145. data/vendor/faiss/faiss/impl/result_handler/avx512.cpp +0 -4
  146. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer-avx2.cpp +0 -4
  147. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer-avx512.cpp +0 -4
  148. data/vendor/faiss/faiss/impl/scalar_quantizer/distance_computers.h +5 -1
  149. data/vendor/faiss/faiss/impl/scalar_quantizer/quantizers.h +28 -52
  150. data/vendor/faiss/faiss/impl/scalar_quantizer/scanners.h +59 -11
  151. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx2.cpp +226 -77
  152. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-impl.h +482 -103
  153. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-spr.cpp +2 -149
  154. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512.cpp +3 -698
  155. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-dispatch.h +66 -57
  156. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-neon.cpp +101 -96
  157. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-rvv.cpp +3185 -144
  158. data/vendor/faiss/faiss/impl/scalar_quantizer/sq8_coefficients.h +48 -0
  159. data/vendor/faiss/faiss/impl/scalar_quantizer/sq8_rvv_kernel.h +546 -0
  160. data/vendor/faiss/faiss/impl/scalar_quantizer/training.cpp +2 -2
  161. data/vendor/faiss/faiss/impl/simd_dispatch.h +7 -0
  162. data/vendor/faiss/faiss/impl/simdlib/simdlib_avx2.h +14 -0
  163. data/vendor/faiss/faiss/impl/simdlib/simdlib_dispatch.h +7 -8
  164. data/vendor/faiss/faiss/impl/sq_fastscan_utils.h +232 -0
  165. data/vendor/faiss/faiss/index_factory.cpp +76 -0
  166. data/vendor/faiss/faiss/invlists/InvertedLists.cpp +3 -1
  167. data/vendor/faiss/faiss/svs/IndexSVSVamana.h +3 -2
  168. data/vendor/faiss/faiss/utils/AlignedTable.h +6 -1
  169. data/vendor/faiss/faiss/utils/Heap.cpp +4 -3
  170. data/vendor/faiss/faiss/utils/approx_topk_hamming/approx_topk_hamming.h +16 -5
  171. data/vendor/faiss/faiss/utils/bf16.h +36 -0
  172. data/vendor/faiss/faiss/utils/distances.cpp +226 -15
  173. data/vendor/faiss/faiss/utils/distances.h +34 -0
  174. data/vendor/faiss/faiss/utils/distances_dispatch.h +6 -4
  175. data/vendor/faiss/faiss/utils/distances_fused/avx512.cpp +2 -5
  176. data/vendor/faiss/faiss/utils/distances_fused/distances_fused.cpp +0 -14
  177. data/vendor/faiss/faiss/utils/distances_fused/rvv.cpp +25 -0
  178. data/vendor/faiss/faiss/utils/distances_fused/simdlib_based.cpp +0 -4
  179. data/vendor/faiss/faiss/utils/distances_fused/simdlib_based_neon.cpp +0 -4
  180. data/vendor/faiss/faiss/utils/distances_fused/simdlib_kernel-inl.h +2 -1
  181. data/vendor/faiss/faiss/utils/distances_simd.cpp +9 -0
  182. data/vendor/faiss/faiss/utils/fp16-fp16c.h +4 -0
  183. data/vendor/faiss/faiss/utils/fp16-inl.h +26 -52
  184. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx2.cpp +0 -4
  185. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512.cpp +0 -4
  186. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512_vpopcnt.cpp +0 -4
  187. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx512.h +33 -5
  188. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx512_vpopcnt.h +15 -30
  189. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-neon.h +0 -4
  190. data/vendor/faiss/faiss/utils/hamming_distance/hamming_impl.h +2 -1
  191. data/vendor/faiss/faiss/utils/hamming_distance/hamming_neon.cpp +0 -4
  192. data/vendor/faiss/faiss/utils/hamming_distance/hamming_rvv.cpp +0 -4
  193. data/vendor/faiss/faiss/utils/pq_code_distance.h +8 -4
  194. data/vendor/faiss/faiss/utils/prefetch.h +1 -26
  195. data/vendor/faiss/faiss/utils/quantize_lut.cpp +26 -27
  196. data/vendor/faiss/faiss/utils/quantize_lut.h +11 -0
  197. data/vendor/faiss/faiss/utils/rabitq_simd.h +51 -16
  198. data/vendor/faiss/faiss/utils/simd_impl/distances_aarch64.cpp +9 -4
  199. data/vendor/faiss/faiss/utils/simd_impl/distances_arm_sve.cpp +37 -5
  200. data/vendor/faiss/faiss/utils/simd_impl/distances_avx2.cpp +64 -5
  201. data/vendor/faiss/faiss/utils/simd_impl/distances_avx512.cpp +64 -5
  202. data/vendor/faiss/faiss/utils/simd_impl/distances_rvv.cpp +353 -71
  203. data/vendor/faiss/faiss/utils/simd_impl/distances_rvv_kernel.h +68 -0
  204. data/vendor/faiss/faiss/utils/simd_impl/exhaustive_L2sqr_blas_cmax.h +93 -0
  205. data/vendor/faiss/faiss/utils/simd_impl/fp16_kernels.h +76 -0
  206. data/vendor/faiss/faiss/utils/simd_impl/fp16_kernels_avx2.cpp +52 -0
  207. data/vendor/faiss/faiss/utils/simd_impl/fp16_kernels_avx512.cpp +52 -0
  208. data/vendor/faiss/faiss/utils/simd_impl/fp16_kernels_neon.cpp +48 -0
  209. data/vendor/faiss/faiss/utils/simd_impl/masked_sum-avx2.h +44 -0
  210. data/vendor/faiss/faiss/utils/simd_impl/masked_sum-avx512.h +43 -0
  211. data/vendor/faiss/faiss/utils/simd_impl/masked_sum.h +38 -0
  212. data/vendor/faiss/faiss/utils/simd_impl/partitioning_avx2.cpp +0 -4
  213. data/vendor/faiss/faiss/utils/simd_impl/partitioning_neon.cpp +0 -4
  214. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx2.cpp +54 -15
  215. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512.cpp +38 -21
  216. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512_vpopcnt.cpp +51 -14
  217. data/vendor/faiss/faiss/utils/simd_impl/rabitq_neon.cpp +297 -10
  218. data/vendor/faiss/faiss/utils/simd_impl/rabitq_rvv.cpp +0 -4
  219. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels_avx2.cpp +0 -4
  220. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels_avx512.cpp +0 -4
  221. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels_sve.cpp +0 -4
  222. data/vendor/faiss/faiss/utils/simd_levels.cpp +19 -3
  223. data/vendor/faiss/faiss/utils/simd_levels.h +74 -1
  224. data/vendor/faiss/faiss/utils/transpose/transpose-avx2-inl.h +0 -4
  225. data/vendor/faiss/faiss/utils/transpose/transpose-avx512-inl.h +0 -4
  226. data/vendor/faiss/faiss/utils/utils.cpp +80 -0
  227. data/vendor/faiss/faiss/utils/utils.h +24 -0
  228. metadata +33 -1
@@ -0,0 +1,855 @@
1
+ /*
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ #include <faiss/IndexIVFSQFastScan.h>
9
+ #include <faiss/impl/sq_fastscan_utils.h>
10
+
11
+ #include <omp.h>
12
+
13
+ #include <algorithm>
14
+ #include <cstring>
15
+ #include <memory>
16
+ #include <vector>
17
+
18
+ #include <faiss/impl/AuxIndexStructures.h>
19
+ #include <faiss/impl/FaissAssert.h>
20
+ #include <faiss/impl/IDSelector.h>
21
+ #include <faiss/impl/ScalarQuantizer.h>
22
+ #include <faiss/impl/fast_scan/fast_scan.h>
23
+ #include <faiss/invlists/BlockInvertedLists.h>
24
+ #include <faiss/invlists/DirectMap.h>
25
+ #include <faiss/invlists/InvertedLists.h>
26
+ #include <faiss/utils/Heap.h>
27
+ #include <faiss/utils/distances.h>
28
+ #include <faiss/utils/hamming.h>
29
+ #include <faiss/utils/utils.h>
30
+
31
+ namespace faiss {
32
+
33
+ using namespace faiss::sq_fastscan;
34
+
35
+ // -----------------------------------------------------------------------
36
+ // Constructors
37
+ // -----------------------------------------------------------------------
38
+
39
+ IndexIVFSQFastScan::IndexIVFSQFastScan(
40
+ Index* quantizer_in,
41
+ size_t d_in,
42
+ size_t nlist_in,
43
+ ScalarQuantizer::QuantizerType qtype,
44
+ MetricType metric,
45
+ int bbs_in,
46
+ bool by_residual_in)
47
+ : IndexIVFFastScan(quantizer_in, d_in, nlist_in, 0, metric, false),
48
+ sq(d_in, qtype) {
49
+ if (is_fallback(qtype)) {
50
+ by_residual = by_residual_in;
51
+ if (ScalarQuantizer::TurboQuantRefine::is_turboq_full(qtype)) {
52
+ by_residual = false;
53
+ }
54
+ } else {
55
+ // FastScan path: by_residual=false because our 2D LUT cannot
56
+ // handle per-probe residuals (same rationale as IndexIVFPQFastScan).
57
+ by_residual = false;
58
+ }
59
+
60
+ if (is_fallback(qtype)) {
61
+ // Fallback: use standard ArrayInvertedLists with full SQ codes.
62
+ // Search uses IndexIVF's InvertedListScanner path.
63
+ code_size = sq.code_size;
64
+ replace_invlists(new ArrayInvertedLists(nlist_in, code_size), true);
65
+ } else {
66
+ size_t M_in = d_in;
67
+ M = M_in;
68
+ nbits = 4;
69
+ bbs = bbs_in;
70
+ ksub = 16;
71
+ M2 = roundup(M_in, 2);
72
+ code_size = M2 / 2;
73
+
74
+ replace_invlists(
75
+ new BlockInvertedLists(nlist_in, get_CodePacker()), true);
76
+
77
+ if (needs_rerank(qtype)) {
78
+ lo_codes_invlists =
79
+ new ArrayInvertedLists(nlist_in, sq_rerank_size(d, qtype));
80
+ direct_map.set_type(DirectMap::Hashtable, invlists, 0);
81
+ }
82
+ }
83
+ }
84
+
85
+ IndexIVFSQFastScan::IndexIVFSQFastScan() = default;
86
+
87
+ IndexIVFSQFastScan::IndexIVFSQFastScan(
88
+ const IndexIVFScalarQuantizer& orig,
89
+ int bbs_in)
90
+ : IndexIVFFastScan(
91
+ orig.quantizer,
92
+ orig.d,
93
+ orig.nlist,
94
+ 0,
95
+ orig.metric_type,
96
+ false),
97
+ sq(orig.sq) {
98
+ ntotal = orig.ntotal;
99
+ is_trained = orig.is_trained;
100
+ nprobe = orig.nprobe;
101
+
102
+ if (is_fallback(sq.qtype)) {
103
+ by_residual = orig.by_residual;
104
+ code_size = sq.code_size;
105
+ replace_invlists(new ArrayInvertedLists(nlist, code_size), true);
106
+
107
+ for (size_t list_no = 0; list_no < nlist; list_no++) {
108
+ size_t list_size = orig.invlists->list_size(list_no);
109
+ if (list_size == 0) {
110
+ continue;
111
+ }
112
+ InvertedLists::ScopedCodes codes(orig.invlists, list_no);
113
+ InvertedLists::ScopedIds ids(orig.invlists, list_no);
114
+ invlists->add_entries(list_no, list_size, ids.get(), codes.get());
115
+ }
116
+ return;
117
+ }
118
+
119
+ FAISS_THROW_IF_NOT_MSG(
120
+ !orig.by_residual,
121
+ "IndexIVFSQFastScan: conversion from IndexIVFScalarQuantizer "
122
+ "with by_residual=true is not supported for native/rerank "
123
+ "types. Set orig.by_residual=false and retrain, or construct "
124
+ "IndexIVFSQFastScan directly.");
125
+
126
+ by_residual = false;
127
+
128
+ size_t M_in = d;
129
+ M = M_in;
130
+ nbits = 4;
131
+ bbs = bbs_in;
132
+ ksub = 16;
133
+ M2 = roundup(M_in, 2);
134
+ code_size = M2 / 2;
135
+
136
+ replace_invlists(new BlockInvertedLists(nlist, get_CodePacker()), true);
137
+
138
+ if (needs_rerank(sq.qtype)) {
139
+ lo_codes_invlists = new ArrayInvertedLists(nlist, lo_code_size());
140
+ direct_map.set_type(DirectMap::Hashtable, invlists, 0);
141
+ }
142
+
143
+ for (size_t list_no = 0; list_no < nlist; list_no++) {
144
+ size_t list_size = orig.invlists->list_size(list_no);
145
+ if (list_size == 0) {
146
+ continue;
147
+ }
148
+
149
+ InvertedLists::ScopedCodes orig_codes(orig.invlists, list_no);
150
+ InvertedLists::ScopedIds orig_ids(orig.invlists, list_no);
151
+
152
+ // Decode to float and re-encode for the scan.
153
+ std::vector<float> recon(list_size * d);
154
+ sq.decode(orig_codes.get(), recon.data(), list_size);
155
+
156
+ std::vector<uint8_t> flat4(list_size * M2 / 2, 0);
157
+ if (is_native_4bit(sq.qtype)) {
158
+ sq.compute_codes(recon.data(), flat4.data(), list_size);
159
+ } else {
160
+ // Top 4 bits feed the scan, the rest are kept for rerank.
161
+ std::vector<uint8_t> lo(list_size * lo_code_size(), 0);
162
+ float_to_split_codes(
163
+ recon.data(),
164
+ flat4.data(),
165
+ lo.data(),
166
+ list_size,
167
+ d,
168
+ M2,
169
+ sq);
170
+ lo_codes_invlists->add_entries(
171
+ list_no, list_size, orig_ids.get(), lo.data());
172
+ for (size_t j = 0; j < list_size; j++) {
173
+ direct_map.add_single_id(orig_ids.get()[j], list_no, j);
174
+ }
175
+ }
176
+
177
+ BlockInvertedLists* bil = dynamic_cast<BlockInvertedLists*>(invlists);
178
+ FAISS_THROW_IF_NOT(bil);
179
+
180
+ size_t nb2 = roundup(list_size, bbs);
181
+ AlignedTable<uint8_t> packed(nb2 * M2 / 2);
182
+ pq4_pack_codes(flat4.data(), list_size, M, nb2, bbs, M2, packed.get());
183
+ bil->add_entries(list_no, list_size, orig_ids.get(), packed.get());
184
+ }
185
+ }
186
+
187
+ IndexIVFSQFastScan::~IndexIVFSQFastScan() {
188
+ delete lo_codes_invlists;
189
+ }
190
+
191
+ // -----------------------------------------------------------------------
192
+ // Virtual method implementations
193
+ // -----------------------------------------------------------------------
194
+
195
+ size_t IndexIVFSQFastScan::fast_scan_code_size() const {
196
+ return M2 / 2;
197
+ }
198
+
199
+ void IndexIVFSQFastScan::train_encoder(
200
+ idx_t n,
201
+ const float* x,
202
+ const idx_t* /*assign*/) {
203
+ sq.train(n, x);
204
+ }
205
+
206
+ idx_t IndexIVFSQFastScan::train_encoder_num_vectors() const {
207
+ return 100000;
208
+ }
209
+
210
+ void IndexIVFSQFastScan::encode_vectors(
211
+ idx_t n,
212
+ const float* x,
213
+ const idx_t* list_nos,
214
+ uint8_t* codes_out,
215
+ bool include_listnos) const {
216
+ std::vector<float> residuals;
217
+ const float* to_encode = x;
218
+
219
+ if (by_residual) {
220
+ residuals.resize(n * d);
221
+ for (idx_t i = 0; i < n; i++) {
222
+ if (list_nos[i] >= 0) {
223
+ quantizer->compute_residual(
224
+ x + i * d, residuals.data() + i * d, list_nos[i]);
225
+ } else {
226
+ memset(residuals.data() + i * d, 0, sizeof(float) * d);
227
+ }
228
+ }
229
+ to_encode = residuals.data();
230
+ }
231
+
232
+ if (is_fallback(sq.qtype)) {
233
+ sq.compute_codes(to_encode, codes_out, n);
234
+ } else if (is_native_4bit(sq.qtype)) {
235
+ sq.compute_codes(to_encode, codes_out, n);
236
+ } else {
237
+ // add_core keeps the leftover bits in lo_codes_invlists.
238
+ std::vector<uint8_t> lo_scratch(size_t(n) * lo_code_size());
239
+ float_to_split_codes(
240
+ to_encode, codes_out, lo_scratch.data(), n, d, M2, sq);
241
+ }
242
+
243
+ if (include_listnos) {
244
+ size_t coarse_size = coarse_code_size();
245
+ for (idx_t i = n - 1; i >= 0; i--) {
246
+ uint8_t* code = codes_out + i * (coarse_size + code_size);
247
+ memmove(code + coarse_size, codes_out + i * code_size, code_size);
248
+ encode_listno(list_nos[i], code);
249
+ }
250
+ }
251
+ }
252
+
253
+ bool IndexIVFSQFastScan::lookup_table_is_3d() const {
254
+ // For SQ, the LUT doesn't depend on which list we're scanning
255
+ // (unlike PQ with residuals and precomputed tables).
256
+ return false;
257
+ }
258
+
259
+ void IndexIVFSQFastScan::compute_LUT(
260
+ size_t n,
261
+ const float* x,
262
+ const CoarseQuantized& /*cq*/,
263
+ AlignedTable<float>& dis_tables,
264
+ AlignedTable<float>& /*biases*/,
265
+ const FastScanDistancePostProcessing&) const {
266
+ size_t dim = d;
267
+ size_t ksub_val = 16;
268
+ size_t dim12 = dim * ksub_val;
269
+
270
+ // Build reconstruction table: for each dimension, the 16 centroid values.
271
+ bool is_uniform = is_uniform_range(sq.qtype) ||
272
+ sq.qtype == ScalarQuantizer::QT_4bit_uniform;
273
+
274
+ std::vector<float> recon_table(dim * ksub_val);
275
+
276
+ if (is_uniform) {
277
+ float vmin, vdiff;
278
+ get_uniform_range(sq, vmin, vdiff);
279
+ for (size_t c = 0; c < ksub_val; c++) {
280
+ float recon = vmin + ((c + 0.5f) / 15.0f) * vdiff;
281
+ for (size_t m = 0; m < dim; m++) {
282
+ recon_table[m * ksub_val + c] = recon;
283
+ }
284
+ }
285
+ } else {
286
+ const float* vmin = sq.trained.data();
287
+ const float* vdiff = sq.trained.data() + dim;
288
+ for (size_t m = 0; m < dim; m++) {
289
+ for (size_t c = 0; c < ksub_val; c++) {
290
+ recon_table[m * ksub_val + c] =
291
+ vmin[m] + ((c + 0.5f) / 15.0f) * vdiff[m];
292
+ }
293
+ }
294
+ }
295
+
296
+ // by_residual is always false for the fast-scan path (non-fallback
297
+ // types), so codes store raw quantized values and the LUT computes
298
+ // distances directly from the raw query. No biases needed.
299
+
300
+ dis_tables.resize(n * dim12);
301
+
302
+ if (metric_type == METRIC_L2) {
303
+ for (size_t i = 0; i < n; i++) {
304
+ const float* xi = x + i * dim;
305
+ float* lut_i = dis_tables.get() + i * dim12;
306
+ for (size_t m = 0; m < dim; m++) {
307
+ float qi = xi[m];
308
+ const float* recon_m = recon_table.data() + m * ksub_val;
309
+ float* lut_m = lut_i + m * ksub_val;
310
+ for (size_t c = 0; c < ksub_val; c++) {
311
+ float diff = qi - recon_m[c];
312
+ lut_m[c] = diff * diff;
313
+ }
314
+ }
315
+ }
316
+ } else {
317
+ for (size_t i = 0; i < n; i++) {
318
+ const float* xi = x + i * dim;
319
+ float* lut_i = dis_tables.get() + i * dim12;
320
+ for (size_t m = 0; m < dim; m++) {
321
+ float qi = xi[m];
322
+ const float* recon_m = recon_table.data() + m * ksub_val;
323
+ float* lut_m = lut_i + m * ksub_val;
324
+ for (size_t c = 0; c < ksub_val; c++) {
325
+ lut_m[c] = qi * recon_m[c];
326
+ }
327
+ }
328
+ }
329
+ }
330
+ }
331
+
332
+ void IndexIVFSQFastScan::sa_decode(idx_t n, const uint8_t* bytes, float* x)
333
+ const {
334
+ size_t coarse_size = coarse_code_size();
335
+
336
+ #pragma omp parallel if (n > 1)
337
+ {
338
+ std::vector<float> residual(d);
339
+
340
+ #pragma omp for
341
+ for (idx_t i = 0; i < n; i++) {
342
+ const uint8_t* code = bytes + i * (code_size + coarse_size);
343
+ int64_t list_no = decode_listno(code);
344
+ float* xi = x + i * d;
345
+ sq.decode(code + coarse_size, xi, 1);
346
+ if (by_residual) {
347
+ quantizer->reconstruct(list_no, residual.data());
348
+ for (int j = 0; j < d; j++) {
349
+ xi[j] += residual[j];
350
+ }
351
+ }
352
+ }
353
+ }
354
+ }
355
+
356
+ void IndexIVFSQFastScan::recombine_code(
357
+ int64_t list_no,
358
+ int64_t offset,
359
+ uint8_t* scratch,
360
+ int* values,
361
+ uint8_t* code_out) const {
362
+ // Interleaved for SIMD, so read one sub-quantizer at a time.
363
+ memset(scratch, 0, size_t(M2) / 2);
364
+ InvertedLists::ScopedCodes list_codes(invlists, list_no);
365
+ BitstringWriter bsw(scratch, M2 / 2);
366
+ for (size_t m = 0; m < M; m++) {
367
+ bsw.write(
368
+ pq4_get_packed_element(list_codes.get(), bbs, M2, offset, m),
369
+ 4);
370
+ }
371
+ InvertedLists::ScopedCodes lo(lo_codes_invlists, list_no);
372
+ split_to_codes(
373
+ scratch, lo.get() + offset * lo_code_size(), d, sq.qtype, values);
374
+ // BitstringWriter matches Codec6bit/Codec8bit, so distance_to_code reads
375
+ // it.
376
+ memset(code_out, 0, sq.code_size);
377
+ BitstringWriter out(code_out, sq.code_size);
378
+ const int bits = sq_bits(sq.qtype);
379
+ for (int m = 0; m < d; m++) {
380
+ out.write(values[m], bits);
381
+ }
382
+ }
383
+
384
+ void IndexIVFSQFastScan::reconstruct_from_offset(
385
+ int64_t list_no,
386
+ int64_t offset,
387
+ float* recons) const {
388
+ if (is_fallback(sq.qtype)) {
389
+ // Fallback: codes stored directly in invlists.
390
+ InvertedLists::ScopedCodes codes(invlists, list_no);
391
+ sq.decode(codes.get() + offset * sq.code_size, recons, 1);
392
+ } else if (lo_codes_invlists) {
393
+ // Rerank: rebuild the original code from its two nibble halves.
394
+ std::vector<uint8_t> scratch(M2 / 2), code(sq.code_size);
395
+ std::vector<int> values(d);
396
+ recombine_code(
397
+ list_no, offset, scratch.data(), values.data(), code.data());
398
+ sq.decode(code.data(), recons, 1);
399
+ } else {
400
+ // Native 4-bit: unpack from block inverted lists.
401
+ std::vector<uint8_t> code(M2 / 2, 0);
402
+ InvertedLists::ScopedCodes list_codes(invlists, list_no);
403
+ BitstringWriter bsw(code.data(), M2 / 2);
404
+ for (size_t m = 0; m < M; m++) {
405
+ uint8_t c = pq4_get_packed_element(
406
+ list_codes.get(), bbs, M2, offset, m);
407
+ bsw.write(c, 4);
408
+ }
409
+ sq.decode(code.data(), recons, 1);
410
+ }
411
+
412
+ if (by_residual) {
413
+ std::vector<float> centroid(d);
414
+ quantizer->reconstruct(list_no, centroid.data());
415
+ for (int j = 0; j < d; j++) {
416
+ recons[j] += centroid[j];
417
+ }
418
+ }
419
+ }
420
+
421
+ InvertedListScanner* IndexIVFSQFastScan::get_InvertedListScanner(
422
+ bool store_pairs,
423
+ const IDSelector* sel,
424
+ const IVFSearchParameters*) const {
425
+ return sq.select_InvertedListScanner(
426
+ metric_type, quantizer, store_pairs, sel, by_residual);
427
+ }
428
+
429
+ // -----------------------------------------------------------------------
430
+ // search — fallback types use IndexIVF's scanner-based path
431
+ // -----------------------------------------------------------------------
432
+
433
+ void IndexIVFSQFastScan::search(
434
+ idx_t n,
435
+ const float* x,
436
+ idx_t k,
437
+ float* distances,
438
+ idx_t* labels,
439
+ const SearchParameters* params) const {
440
+ if (is_fallback(sq.qtype)) {
441
+ IndexIVF::search(n, x, k, distances, labels, params);
442
+ } else if (needs_rerank(sq.qtype)) {
443
+ idx_t k_coarse = idx_t(double(k) * rerank_factor);
444
+ FAISS_THROW_IF_NOT(k_coarse >= k);
445
+ FAISS_THROW_IF_NOT_MSG(
446
+ n <= INT64_MAX / k_coarse, "n * k_coarse would overflow int64");
447
+
448
+ std::vector<float> coarse_dis(n * k_coarse);
449
+ std::vector<idx_t> coarse_ids(n * k_coarse);
450
+ IndexIVFFastScan::search(
451
+ n, x, k_coarse, coarse_dis.data(), coarse_ids.data(), params);
452
+
453
+ // Rerank using original full-precision codes
454
+ #pragma omp parallel
455
+ {
456
+ std::unique_ptr<ScalarQuantizer::SQDistanceComputer> dc(
457
+ sq.get_distance_computer(metric_type));
458
+ std::vector<uint8_t> scratch(M2 / 2), code(sq.code_size);
459
+ std::vector<int> values(d);
460
+
461
+ #pragma omp for
462
+ for (idx_t i = 0; i < n; i++) {
463
+ const float* xi = x + i * d;
464
+ dc->set_query(xi);
465
+
466
+ float* heap_dis = distances + i * k;
467
+ idx_t* heap_ids = labels + i * k;
468
+
469
+ if (metric_type == METRIC_L2) {
470
+ maxheap_heapify(k, heap_dis, heap_ids);
471
+ } else {
472
+ minheap_heapify(k, heap_dis, heap_ids);
473
+ }
474
+
475
+ for (idx_t j = 0; j < k_coarse; j++) {
476
+ idx_t id = coarse_ids[i * k_coarse + j];
477
+ if (id < 0) {
478
+ continue;
479
+ }
480
+
481
+ idx_t lo = direct_map.get(id);
482
+ idx_t list_no = lo_listno(lo);
483
+ idx_t offset = lo_offset(lo);
484
+
485
+ recombine_code(
486
+ list_no,
487
+ offset,
488
+ scratch.data(),
489
+ values.data(),
490
+ code.data());
491
+ float dis = dc->distance_to_code(code.data());
492
+
493
+ if (metric_type == METRIC_L2) {
494
+ if (dis < heap_dis[0]) {
495
+ maxheap_replace_top(k, heap_dis, heap_ids, dis, id);
496
+ }
497
+ } else {
498
+ if (dis > heap_dis[0]) {
499
+ minheap_replace_top(k, heap_dis, heap_ids, dis, id);
500
+ }
501
+ }
502
+ }
503
+
504
+ if (metric_type == METRIC_L2) {
505
+ maxheap_reorder(k, heap_dis, heap_ids);
506
+ } else {
507
+ minheap_reorder(k, heap_dis, heap_ids);
508
+ }
509
+ }
510
+ }
511
+ } else {
512
+ IndexIVFFastScan::search(n, x, k, distances, labels, params);
513
+ }
514
+ }
515
+
516
+ void IndexIVFSQFastScan::search_preassigned(
517
+ idx_t n,
518
+ const float* x,
519
+ idx_t k,
520
+ const idx_t* assign,
521
+ const float* centroid_dis,
522
+ float* distances,
523
+ idx_t* labels,
524
+ bool store_pairs,
525
+ const IVFSearchParameters* params,
526
+ IndexIVFStats* stats) const {
527
+ if (is_fallback(sq.qtype)) {
528
+ IndexIVF::search_preassigned(
529
+ n,
530
+ x,
531
+ k,
532
+ assign,
533
+ centroid_dis,
534
+ distances,
535
+ labels,
536
+ store_pairs,
537
+ params,
538
+ stats);
539
+ } else if (needs_rerank(sq.qtype)) {
540
+ idx_t k_coarse = idx_t(double(k) * rerank_factor);
541
+ FAISS_THROW_IF_NOT(k_coarse >= k);
542
+ FAISS_THROW_IF_NOT_MSG(
543
+ n <= INT64_MAX / k_coarse, "n * k_coarse would overflow int64");
544
+
545
+ std::vector<float> coarse_dis(n * k_coarse);
546
+ std::vector<idx_t> coarse_ids(n * k_coarse);
547
+ IndexIVFFastScan::search_preassigned(
548
+ n,
549
+ x,
550
+ k_coarse,
551
+ assign,
552
+ centroid_dis,
553
+ coarse_dis.data(),
554
+ coarse_ids.data(),
555
+ false,
556
+ params,
557
+ stats);
558
+
559
+ #pragma omp parallel
560
+ {
561
+ std::unique_ptr<ScalarQuantizer::SQDistanceComputer> dc(
562
+ sq.get_distance_computer(metric_type));
563
+ std::vector<uint8_t> scratch(M2 / 2), code(sq.code_size);
564
+ std::vector<int> values(d);
565
+
566
+ #pragma omp for
567
+ for (idx_t i = 0; i < n; i++) {
568
+ const float* xi = x + i * d;
569
+ dc->set_query(xi);
570
+
571
+ float* heap_dis = distances + i * k;
572
+ idx_t* heap_ids = labels + i * k;
573
+
574
+ if (metric_type == METRIC_L2) {
575
+ maxheap_heapify(k, heap_dis, heap_ids);
576
+ } else {
577
+ minheap_heapify(k, heap_dis, heap_ids);
578
+ }
579
+
580
+ for (idx_t j = 0; j < k_coarse; j++) {
581
+ idx_t id = coarse_ids[i * k_coarse + j];
582
+ if (id < 0) {
583
+ continue;
584
+ }
585
+
586
+ idx_t lo = direct_map.get(id);
587
+ idx_t list_no = lo_listno(lo);
588
+ idx_t offset = lo_offset(lo);
589
+
590
+ recombine_code(
591
+ list_no,
592
+ offset,
593
+ scratch.data(),
594
+ values.data(),
595
+ code.data());
596
+ float dis = dc->distance_to_code(code.data());
597
+
598
+ if (metric_type == METRIC_L2) {
599
+ if (dis < heap_dis[0]) {
600
+ maxheap_replace_top(k, heap_dis, heap_ids, dis, id);
601
+ }
602
+ } else {
603
+ if (dis > heap_dis[0]) {
604
+ minheap_replace_top(k, heap_dis, heap_ids, dis, id);
605
+ }
606
+ }
607
+ }
608
+
609
+ if (metric_type == METRIC_L2) {
610
+ maxheap_reorder(k, heap_dis, heap_ids);
611
+ } else {
612
+ minheap_reorder(k, heap_dis, heap_ids);
613
+ }
614
+ }
615
+ }
616
+ } else {
617
+ IndexIVFFastScan::search_preassigned(
618
+ n,
619
+ x,
620
+ k,
621
+ assign,
622
+ centroid_dis,
623
+ distances,
624
+ labels,
625
+ store_pairs,
626
+ params,
627
+ stats);
628
+ }
629
+ }
630
+
631
+ void IndexIVFSQFastScan::range_search(
632
+ idx_t n,
633
+ const float* x,
634
+ float radius,
635
+ RangeSearchResult* result,
636
+ const SearchParameters* params) const {
637
+ if (is_fallback(sq.qtype)) {
638
+ IndexIVF::range_search(n, x, radius, result, params);
639
+ } else if (needs_rerank(sq.qtype)) {
640
+ const IVFSearchParameters* ivf_params = nullptr;
641
+ if (params) {
642
+ ivf_params = dynamic_cast<const IVFSearchParameters*>(params);
643
+ }
644
+ size_t cur_nprobe = ivf_params ? ivf_params->nprobe : nprobe;
645
+ const IDSelector* sel = params ? params->sel : nullptr;
646
+
647
+ std::vector<idx_t> assign(n * cur_nprobe);
648
+ std::vector<float> centroid_dis(n * cur_nprobe);
649
+ quantizer->search(n, x, cur_nprobe, centroid_dis.data(), assign.data());
650
+
651
+ std::vector<RangeSearchPartialResult*> partial_results(
652
+ omp_get_max_threads());
653
+
654
+ #pragma omp parallel
655
+ {
656
+ int rank = omp_get_thread_num();
657
+ RangeSearchPartialResult* pres =
658
+ new RangeSearchPartialResult(result);
659
+ partial_results[rank] = pres;
660
+
661
+ std::unique_ptr<ScalarQuantizer::SQDistanceComputer> dc(
662
+ sq.get_distance_computer(metric_type));
663
+ std::vector<uint8_t> scratch(M2 / 2), code(sq.code_size);
664
+ std::vector<int> values(d);
665
+
666
+ #pragma omp for
667
+ for (idx_t i = 0; i < n; i++) {
668
+ dc->set_query(x + i * d);
669
+ RangeQueryResult& qres = pres->new_result(i);
670
+
671
+ for (size_t p = 0; p < cur_nprobe; p++) {
672
+ idx_t list_no = assign[i * cur_nprobe + p];
673
+ if (list_no < 0) {
674
+ continue;
675
+ }
676
+ size_t list_size = lo_codes_invlists->list_size(list_no);
677
+ if (list_size == 0) {
678
+ continue;
679
+ }
680
+ InvertedLists::ScopedIds ids(lo_codes_invlists, list_no);
681
+
682
+ for (size_t j = 0; j < list_size; j++) {
683
+ if (sel && !sel->is_member(ids[j])) {
684
+ continue;
685
+ }
686
+ recombine_code(
687
+ list_no,
688
+ j,
689
+ scratch.data(),
690
+ values.data(),
691
+ code.data());
692
+ float dis = dc->distance_to_code(code.data());
693
+ if (metric_type == METRIC_L2) {
694
+ if (dis < radius) {
695
+ qres.add(dis, ids[j]);
696
+ }
697
+ } else {
698
+ if (dis > radius) {
699
+ qres.add(dis, ids[j]);
700
+ }
701
+ }
702
+ }
703
+ }
704
+ }
705
+ }
706
+ RangeSearchPartialResult::merge(partial_results);
707
+ } else {
708
+ IndexIVFFastScan::range_search(n, x, radius, result, params);
709
+ }
710
+ }
711
+
712
+ // -----------------------------------------------------------------------
713
+ // add_with_ids — custom implementation to store both packed and original codes
714
+ // -----------------------------------------------------------------------
715
+
716
+ void IndexIVFSQFastScan::add_with_ids(
717
+ idx_t n,
718
+ const float* x,
719
+ const idx_t* xids) {
720
+ FAISS_THROW_IF_NOT(is_trained);
721
+
722
+ constexpr idx_t bs = 65536;
723
+ if (n > bs) {
724
+ for (idx_t i0 = 0; i0 < n; i0 += bs) {
725
+ idx_t i1 = std::min(n, i0 + bs);
726
+ add_with_ids(i1 - i0, x + i0 * d, xids ? xids + i0 : nullptr);
727
+ }
728
+ return;
729
+ }
730
+
731
+ // Assign to coarse clusters
732
+ std::unique_ptr<idx_t[]> idx(new idx_t[n]);
733
+ quantizer->assign(n, x, idx.get());
734
+
735
+ if (is_fallback(sq.qtype)) {
736
+ // Fallback: store SQ codes directly in invlists (same as
737
+ // IndexIVFScalarQuantizer).
738
+ std::vector<uint8_t> flat_codes(n * sq.code_size);
739
+ encode_vectors(n, x, idx.get(), flat_codes.data());
740
+
741
+ for (idx_t i = 0; i < n; i++) {
742
+ if (idx[i] < 0) {
743
+ continue;
744
+ }
745
+ idx_t id = xids ? xids[i] : ntotal + i;
746
+ invlists->add_entry(
747
+ idx[i], id, flat_codes.data() + i * sq.code_size);
748
+ }
749
+ ntotal += n;
750
+ return;
751
+ }
752
+
753
+ // Produce 4-bit packed codes for the SIMD scan,
754
+ // and (for rerank types) also store original codes.
755
+ AlignedTable<uint8_t> flat_codes(n * code_size);
756
+ encode_vectors(n, x, idx.get(), flat_codes.get());
757
+
758
+ // Same quantisation as encode_vectors, or the halves disagree.
759
+ std::vector<uint8_t> lo_flat_codes;
760
+ if (needs_rerank(sq.qtype)) {
761
+ std::vector<float> to_encode(size_t(n) * d);
762
+ for (idx_t i = 0; i < n; i++) {
763
+ if (by_residual && idx[i] >= 0) {
764
+ quantizer->compute_residual(
765
+ x + i * d, to_encode.data() + i * d, idx[i]);
766
+ } else {
767
+ memcpy(to_encode.data() + i * d, x + i * d, sizeof(float) * d);
768
+ }
769
+ }
770
+ lo_flat_codes.assign(size_t(n) * lo_code_size(), 0);
771
+ std::vector<uint8_t> hi_discard(size_t(n) * (M2 / 2));
772
+ float_to_split_codes(
773
+ to_encode.data(),
774
+ hi_discard.data(),
775
+ lo_flat_codes.data(),
776
+ n,
777
+ d,
778
+ M2,
779
+ sq);
780
+ }
781
+
782
+ // Sort by list assignment
783
+ std::vector<idx_t> order(n);
784
+ for (idx_t i = 0; i < n; i++) {
785
+ order[i] = i;
786
+ }
787
+ std::stable_sort(order.begin(), order.end(), [&idx](idx_t a, idx_t b) {
788
+ return idx[a] < idx[b];
789
+ });
790
+
791
+ BlockInvertedLists* bil = dynamic_cast<BlockInvertedLists*>(invlists);
792
+ FAISS_THROW_IF_NOT_MSG(bil, "only block inverted lists supported");
793
+
794
+ idx_t i0 = 0;
795
+ while (i0 < n) {
796
+ idx_t list_no = idx[order[i0]];
797
+ idx_t i1 = i0 + 1;
798
+ while (i1 < n && idx[order[i1]] == list_no) {
799
+ i1++;
800
+ }
801
+
802
+ if (list_no == -1) {
803
+ i0 = i1;
804
+ continue;
805
+ }
806
+
807
+ size_t list_size = bil->list_size(list_no);
808
+ bil->resize(list_no, list_size + i1 - i0);
809
+
810
+ AlignedTable<uint8_t> list_codes((i1 - i0) * code_size);
811
+ for (idx_t i = i0; i < i1; i++) {
812
+ size_t ofs = list_size + i - i0;
813
+ idx_t id = xids ? xids[order[i]] : ntotal + order[i];
814
+ bil->ids[list_no][ofs] = id;
815
+ memcpy(list_codes.data() + (i - i0) * code_size,
816
+ flat_codes.data() + order[i] * code_size,
817
+ code_size);
818
+
819
+ if (needs_rerank(sq.qtype)) {
820
+ lo_codes_invlists->add_entry(
821
+ list_no,
822
+ id,
823
+ lo_flat_codes.data() + order[i] * lo_code_size());
824
+ direct_map.add_single_id(id, list_no, ofs);
825
+ }
826
+ }
827
+
828
+ pq4_pack_codes_range(
829
+ list_codes.data(),
830
+ M,
831
+ list_size,
832
+ list_size + i1 - i0,
833
+ bbs,
834
+ M2,
835
+ bil->codes[list_no].data(),
836
+ 0,
837
+ get_block_stride());
838
+
839
+ i0 = i1;
840
+ }
841
+
842
+ ntotal += n;
843
+ }
844
+
845
+ void IndexIVFSQFastScan::reset() {
846
+ IndexIVFFastScan::reset();
847
+ if (lo_codes_invlists) {
848
+ for (size_t i = 0; i < nlist; i++) {
849
+ lo_codes_invlists->resize(i, 0);
850
+ }
851
+ direct_map.clear();
852
+ }
853
+ }
854
+
855
+ } // namespace faiss