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
@@ -83,5 +83,97 @@ bool runMetalIVFFlatFullSearch(
83
83
  bool centroidsAreFP16 = false,
84
84
  bool waitForCompletion = true);
85
85
 
86
+ /// Full IVF-PQ search on the GPU: builds per-(query, probe) PQ lookup tables,
87
+ /// scans the assigned inverted lists with 8-bit ADC, and merges the per-list
88
+ /// top-k into the final top-k. Coarse quantization is performed on the CPU and
89
+ /// the assignments are passed in via @p coarseAssign. @p coarseDist carries
90
+ /// the per-(query, probe) coarse scores, added for inner product (the IP
91
+ /// tables hold <q_m, pq_m> only); unused for L2.
92
+ bool runMetalIVFPQFullSearch(
93
+ id<MTLDevice> device,
94
+ id<MTLCommandQueue> queue,
95
+ id<MTLBuffer> queries,
96
+ id<MTLBuffer> coarseAssign,
97
+ id<MTLBuffer> coarseDist,
98
+ id<MTLBuffer> coarseCentroids,
99
+ id<MTLBuffer> pqCentroids,
100
+ id<MTLBuffer> lookupTable,
101
+ id<MTLBuffer> codes,
102
+ id<MTLBuffer> ids,
103
+ id<MTLBuffer> listOffset,
104
+ id<MTLBuffer> listLength,
105
+ int nq,
106
+ int d,
107
+ int M,
108
+ int k,
109
+ int nprobe,
110
+ int nlist,
111
+ int avgListLen,
112
+ bool lookupFp16,
113
+ bool isL2,
114
+ id<MTLBuffer> outDistances,
115
+ id<MTLBuffer> outIndices,
116
+ id<MTLBuffer> perListDistBuf,
117
+ id<MTLBuffer> perListIdxBuf,
118
+ bool waitForCompletion = true);
119
+
120
+ /// Build the query-independent precomputed term of the IVFPQ L2 distance
121
+ /// decomposition on the GPU: term2[l][m][c] = ||pq[m][c]||^2 +
122
+ /// 2 <coarse_l[m], pq[m][c]>, laid out as nlist * M * 256 floats. Run once
123
+ /// per trained index; consumed by runMetalIVFPQPrecompSearch.
124
+ bool runMetalIVFPQPrecomputeTerm2(
125
+ id<MTLDevice> device,
126
+ id<MTLCommandQueue> queue,
127
+ id<MTLBuffer> coarseCentroids,
128
+ id<MTLBuffer> pqCentroids,
129
+ id<MTLBuffer> outTerm2,
130
+ int nlist,
131
+ int d,
132
+ int M);
133
+
134
+ /// IVF-PQ search using the precomputed-table decomposition: a per-query
135
+ /// M*256 term is built once per batch (instead of a full residual LUT per
136
+ /// (query, probe)), combined with @p term2 in threadgroup memory by the scan
137
+ /// kernel, and the coarse distance is added as the constant ||x - c||^2 term.
138
+ /// The scan keeps an exact running top-k over arbitrarily long lists and the
139
+ /// merge runs in rounds, so there is no list-length or nprobe*k cap; the
140
+ /// remaining requirements are k <= 512, M <= 64 and d/M <= 256.
141
+ /// onTheFly evaluates encountered codes directly (d <= 256), using the
142
+ /// supplied coarse centroids; shortLists requires every list to have <= 128
143
+ /// entries and selects a smaller threadgroup and scratch allocation.
144
+ /// @p term2 may be nil for inner product (qterm is the whole table);
145
+ /// @p useDis0 adds the coarse distance (required for L2, by_residual for IP).
146
+ bool runMetalIVFPQPrecompSearch(
147
+ id<MTLDevice> device,
148
+ id<MTLCommandQueue> queue,
149
+ id<MTLBuffer> queries,
150
+ id<MTLBuffer> coarseAssign,
151
+ id<MTLBuffer> coarseDist,
152
+ id<MTLBuffer> term2,
153
+ id<MTLBuffer> qtermScratch,
154
+ id<MTLBuffer> pqCentroids,
155
+ id<MTLBuffer> codes,
156
+ id<MTLBuffer> ids,
157
+ id<MTLBuffer> listOffset,
158
+ id<MTLBuffer> listLength,
159
+ int nq,
160
+ int d,
161
+ int M,
162
+ int k,
163
+ int nprobe,
164
+ bool isL2,
165
+ bool useDis0,
166
+ id<MTLBuffer> outDistances,
167
+ id<MTLBuffer> outIndices,
168
+ id<MTLBuffer> perListDistBuf,
169
+ id<MTLBuffer> perListIdxBuf,
170
+ id<MTLBuffer> mergeScratchDistBuf,
171
+ id<MTLBuffer> mergeScratchIdxBuf,
172
+ bool waitForCompletion = true,
173
+ id<MTLBuffer> coarseCentroids = nil,
174
+ bool onTheFly = false,
175
+ bool shortLists = false,
176
+ bool compactMerge = true);
177
+
86
178
  } // namespace gpu_metal
87
179
  } // namespace faiss
@@ -24,6 +24,10 @@ struct MetalIndexConfig {
24
24
 
25
25
  bool useFloat16CoarseQuantizer = false;
26
26
 
27
+ /// Build the IVF-PQ distance lookup tables in half precision. Halves LUT
28
+ /// bandwidth during the GPU scan at a small accuracy cost.
29
+ bool useFloat16 = false;
30
+
27
31
  faiss::gpu::IndicesOptions indicesOptions = faiss::gpu::INDICES_64_BIT;
28
32
 
29
33
  bool interleavedLayout = true;
@@ -0,0 +1,69 @@
1
+ // @lint-ignore-every LICENSELINT
2
+ /**
3
+ * Copyright (c) Meta Platforms, Inc. and its affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ #pragma once
10
+
11
+ #include <faiss/IndexBinaryFlat.h>
12
+ #include <faiss/gpu_metal/MetalResources.h>
13
+ #include <memory>
14
+
15
+ namespace faiss {
16
+ namespace gpu_metal {
17
+
18
+ /// Exhaustive Hamming search over GPU-resident binary vectors.
19
+ /// Every positive dimension divisible by 8 is supported. GPU search is exact
20
+ /// for k <= 512, with ties ordered by increasing label. Larger k, selectors,
21
+ /// and counts exceeding uint32_t use the CPU copy. GPU failures throw rather
22
+ /// than silently falling back. Range search and remove_ids are not supported.
23
+ /// As with other Metal indexes, callers must serialize mutation and search.
24
+ class MetalIndexBinaryFlat : public faiss::IndexBinary {
25
+ public:
26
+ static constexpr int kMaxGpuK = 512;
27
+
28
+ MetalIndexBinaryFlat(std::shared_ptr<MetalResources> resources, int d);
29
+ MetalIndexBinaryFlat(
30
+ std::shared_ptr<MetalResources> resources,
31
+ const faiss::IndexBinaryFlat* index);
32
+ ~MetalIndexBinaryFlat() override;
33
+
34
+ MetalIndexBinaryFlat(const MetalIndexBinaryFlat&) = delete;
35
+ MetalIndexBinaryFlat& operator=(const MetalIndexBinaryFlat&) = delete;
36
+
37
+ void add(idx_t n, const uint8_t* x) override;
38
+ void reset() override;
39
+ void search(
40
+ idx_t n,
41
+ const uint8_t* x,
42
+ idx_t k,
43
+ int32_t* distances,
44
+ idx_t* labels,
45
+ const SearchParameters* params = nullptr) const override;
46
+ void reconstruct(idx_t key, uint8_t* recons) const override;
47
+
48
+ void copyFrom(const faiss::IndexBinaryFlat* index);
49
+ void copyTo(faiss::IndexBinaryFlat* index) const;
50
+
51
+ std::shared_ptr<MetalResources> getResources() const {
52
+ return resources_;
53
+ }
54
+
55
+ private:
56
+ std::shared_ptr<MetalResources> resources_;
57
+ faiss::IndexBinaryFlat cpuIndex_;
58
+ id<MTLBuffer> vectorsBuf_ = nil;
59
+ // Search is synchronous and callers serialize access, so scratch buffers
60
+ // can be reused after waitUntilCompleted. ARC releases them on destruction.
61
+ mutable id<MTLBuffer> queryBuf_ = nil;
62
+ mutable id<MTLBuffer> distanceBuf_ = nil;
63
+ mutable id<MTLBuffer> labelBuf_ = nil;
64
+
65
+ id<MTLBuffer> upload(const uint8_t* data, size_t bytes) const;
66
+ };
67
+
68
+ } // namespace gpu_metal
69
+ } // namespace faiss
@@ -75,13 +75,51 @@ class MetalIndexIVFPQ : public MetalIndex {
75
75
  std::unique_ptr<faiss::IndexIVFPQ> cpuIndex_;
76
76
  std::unique_ptr<MetalIVFPQImpl> gpuIvf_;
77
77
 
78
+ // Reusable GPU scratch buffers for the search path, grown on demand.
79
+ mutable id<MTLBuffer> searchQueriesBuf_ = nil;
80
+ mutable id<MTLBuffer> searchCoarseBuf_ = nil;
81
+ mutable id<MTLBuffer> searchOutDistBuf_ = nil;
82
+ mutable id<MTLBuffer> searchOutIdxBuf_ = nil;
83
+ mutable id<MTLBuffer> searchPerListDistBuf_ = nil;
84
+ mutable id<MTLBuffer> searchPerListIdxBuf_ = nil;
85
+ mutable id<MTLBuffer> lookupTableBuf_ = nil;
86
+ mutable id<MTLBuffer> lookupTableHalfBuf_ = nil;
87
+ mutable id<MTLBuffer> searchCoarseDistBuf_ = nil;
88
+ mutable id<MTLBuffer> searchQTermBuf_ = nil;
89
+ mutable id<MTLBuffer> searchMergeDistBuf_ = nil;
90
+ mutable id<MTLBuffer> searchMergeIdxBuf_ = nil;
91
+ mutable size_t searchQueriesCap_ = 0;
92
+ mutable size_t searchCoarseCap_ = 0;
93
+ mutable size_t searchOutDistCap_ = 0;
94
+ mutable size_t searchOutIdxCap_ = 0;
95
+ mutable size_t searchPerListDistCap_ = 0;
96
+ mutable size_t searchPerListIdxCap_ = 0;
97
+ mutable size_t lookupTableCap_ = 0;
98
+ mutable size_t lookupTableHalfCap_ = 0;
99
+ mutable size_t searchCoarseDistCap_ = 0;
100
+ mutable size_t searchQTermCap_ = 0;
101
+ mutable size_t searchMergeDistCap_ = 0;
102
+ mutable size_t searchMergeIdxCap_ = 0;
103
+
104
+ // Coarse centroids mirrored on the GPU for the L2 lookup-table build.
105
+ mutable id<MTLBuffer> centroidBuf_ = nil;
106
+
107
+ // Query-independent term of the L2 precomputed-table decomposition
108
+ // (nlist * M * 256 floats), built once per trained index on the GPU.
109
+ mutable id<MTLBuffer> term2Buf_ = nil;
110
+
78
111
  void verifyPQSettings_() const;
112
+ void precomputeTerm2_() const;
79
113
 
80
114
  void encodeResidualAndAppend_(
81
115
  idx_t n,
82
116
  const float* x,
83
117
  const idx_t* list_nos,
84
118
  const idx_t* xids);
119
+
120
+ void ensureSearchBuf_(id<MTLBuffer>& buf, size_t& cap, size_t needed) const;
121
+ void uploadCentroids_() const;
122
+ void uploadPQCentroids_() const;
85
123
  };
86
124
 
87
125
  } // namespace gpu_metal
@@ -91,6 +91,112 @@ class MetalKernels {
91
91
  id<MTLBuffer> paramsBuf,
92
92
  int nq);
93
93
 
94
+ // ---- IVF-PQ ----
95
+
96
+ void encodeIVFPQBuildLookupTables(
97
+ id<MTLComputeCommandEncoder> enc,
98
+ bool isL2,
99
+ bool outFp16,
100
+ id<MTLBuffer> queries,
101
+ id<MTLBuffer> coarseAssign,
102
+ id<MTLBuffer> coarseCentroids,
103
+ id<MTLBuffer> pqCentroids,
104
+ id<MTLBuffer> outLookup,
105
+ int nq,
106
+ int d,
107
+ int M,
108
+ int nprobe);
109
+
110
+ void encodeIVFPQScanList(
111
+ id<MTLComputeCommandEncoder> enc,
112
+ bool useSmall,
113
+ bool useFp16Lookup,
114
+ id<MTLBuffer> lookupTable,
115
+ id<MTLBuffer> codes,
116
+ id<MTLBuffer> ids,
117
+ id<MTLBuffer> listOffset,
118
+ id<MTLBuffer> listLength,
119
+ id<MTLBuffer> coarseAssign,
120
+ id<MTLBuffer> perListDist,
121
+ id<MTLBuffer> perListIdx,
122
+ id<MTLBuffer> paramsBuf,
123
+ id<MTLBuffer> coarseDist,
124
+ int nq,
125
+ int nprobe);
126
+
127
+ // ---- IVF-PQ precomputed-table path ----
128
+
129
+ void encodeIVFPQPrecomputeTerm2(
130
+ id<MTLComputeCommandEncoder> enc,
131
+ id<MTLBuffer> coarseCentroids,
132
+ id<MTLBuffer> pqCentroids,
133
+ id<MTLBuffer> outTerm2,
134
+ int nlist,
135
+ int d,
136
+ int M);
137
+
138
+ void encodeIVFPQBuildQueryTerm(
139
+ id<MTLComputeCommandEncoder> enc,
140
+ id<MTLBuffer> queries,
141
+ id<MTLBuffer> pqCentroids,
142
+ id<MTLBuffer> outQTerm,
143
+ int nq,
144
+ int d,
145
+ int M,
146
+ bool isL2);
147
+
148
+ void encodeIVFPQScanListPrecomp(
149
+ id<MTLComputeCommandEncoder> enc,
150
+ id<MTLBuffer> term2, // nil when useTerm2 is false (IP path)
151
+ id<MTLBuffer> qterm,
152
+ id<MTLBuffer> coarseDist,
153
+ id<MTLBuffer> codes,
154
+ id<MTLBuffer> ids,
155
+ id<MTLBuffer> listOffset,
156
+ id<MTLBuffer> listLength,
157
+ id<MTLBuffer> coarseAssign,
158
+ id<MTLBuffer> perListDist,
159
+ id<MTLBuffer> perListIdx,
160
+ int nq,
161
+ int M,
162
+ int k,
163
+ int nprobe,
164
+ bool wantMin,
165
+ bool useTerm2,
166
+ bool useDis0,
167
+ id<MTLBuffer> queries,
168
+ id<MTLBuffer> coarseCentroids,
169
+ id<MTLBuffer> pqCentroids,
170
+ int d,
171
+ bool onTheFly,
172
+ bool shortLists);
173
+
174
+ void encodeIVFMergeListsGrouped(
175
+ id<MTLComputeCommandEncoder> enc,
176
+ id<MTLBuffer> inDist,
177
+ id<MTLBuffer> inIdx,
178
+ id<MTLBuffer> outDist,
179
+ id<MTLBuffer> outIdx,
180
+ int nq,
181
+ int numLists,
182
+ int groupSize,
183
+ int k,
184
+ bool wantMin,
185
+ bool compact);
186
+
187
+ // Exact Hamming top-k from the precompiled MetalDistance.metallib.
188
+ // Requires positive uint32-sized counts and 1 <= k <= 512.
189
+ void encodeHammingDistanceTopK(
190
+ id<MTLComputeCommandEncoder> enc,
191
+ id<MTLBuffer> queries,
192
+ id<MTLBuffer> database,
193
+ id<MTLBuffer> outDist,
194
+ id<MTLBuffer> outIdx,
195
+ uint32_t nq,
196
+ uint32_t nb,
197
+ uint32_t codeSize,
198
+ uint32_t k);
199
+
94
200
  static int selectTopKVariantIndex(int k);
95
201
 
96
202
  private:
@@ -12,6 +12,7 @@
12
12
  #pragma once
13
13
 
14
14
  #include <faiss/Index.h>
15
+ #include <faiss/IndexBinary.h>
15
16
 
16
17
  namespace faiss {
17
18
  namespace gpu_metal {
@@ -41,5 +42,14 @@ faiss::Index* index_cpu_to_gpu(
41
42
  /// Copy Metal index back to CPU. Caller owns returned index.
42
43
  faiss::Index* index_gpu_to_cpu(const faiss::Index* index);
43
44
 
45
+ /// Clone CPU IndexBinaryFlat to Metal. Caller owns the returned index.
46
+ faiss::IndexBinary* index_binary_cpu_to_gpu(
47
+ StandardMetalResourcesHolder* res,
48
+ int device,
49
+ const faiss::IndexBinary* index);
50
+
51
+ /// Clone MetalIndexBinaryFlat back to CPU. Caller owns the returned index.
52
+ faiss::IndexBinary* index_binary_gpu_to_cpu(const faiss::IndexBinary* index);
53
+
44
54
  } // namespace gpu_metal
45
55
  } // namespace faiss
@@ -246,6 +246,8 @@ void TimeoutCallback::set_timeout(double timeout_in_seconds) {
246
246
  }
247
247
 
248
248
  bool TimeoutCallback::want_interrupt() {
249
+ static std::mutex mutex;
250
+ std::lock_guard<std::mutex> guard(mutex);
249
251
  if (timeout == 0) {
250
252
  return false;
251
253
  }
@@ -146,12 +146,7 @@ struct FAISS_API InterruptCallback {
146
146
 
147
147
  static void clear_instance();
148
148
 
149
- /** check if:
150
- * - an interrupt callback is set
151
- * - the callback returns true
152
- * if this is the case, then throw an exception. Should not be called
153
- * from multiple threads.
154
- */
149
+ /// Concurrent calls require a thread-safe want_interrupt().
155
150
  static void check();
156
151
 
157
152
  /// same as check() but return true if is interrupted instead of
@@ -7,6 +7,8 @@
7
7
 
8
8
  #include <faiss/impl/ClusteringHelpers.h>
9
9
 
10
+ #include <algorithm>
11
+ #include <atomic>
10
12
  #include <cassert>
11
13
  #include <chrono>
12
14
  #include <cinttypes>
@@ -20,11 +22,88 @@
20
22
 
21
23
  #include <faiss/Index.h>
22
24
  #include <faiss/impl/FaissAssert.h>
25
+ #include <faiss/impl/simd_dispatch.h>
23
26
  #include <faiss/utils/random.h>
27
+ #include <faiss/utils/simd_impl/fp16_kernels.h>
24
28
 
25
29
  namespace faiss {
26
30
  namespace detail {
27
31
 
32
+ namespace {
33
+
34
+ #if defined(_OPENMP) && !defined(FAISS_USE_OMP_MOCK)
35
+ /// Below this many values, the fp16 finiteness check stays on one thread.
36
+ constexpr size_t fp16_parallel_threshold = size_t(1) << 16;
37
+ #endif
38
+
39
+ /** Shared body of compute_centroids and compute_centroids_fp16.
40
+ *
41
+ * `add_row(i, w, c, scratch)` adds training vector i to the fp32 vector c,
42
+ * scaled by *w when w is not null. `scratch` is a per-thread buffer of d
43
+ * floats.
44
+ */
45
+ template <class AddRow>
46
+ void compute_centroids_impl(
47
+ size_t d,
48
+ size_t k,
49
+ size_t n,
50
+ size_t k_frozen,
51
+ const int64_t* assign,
52
+ const float* weights,
53
+ float* hassign,
54
+ float* centroids,
55
+ const AddRow& add_row) {
56
+ k -= k_frozen;
57
+ centroids += k_frozen * d;
58
+
59
+ memset(centroids, 0, sizeof(*centroids) * d * k);
60
+
61
+ std::atomic<bool> invalid_assignment{false};
62
+ const int64_t num_centroids = static_cast<int64_t>(k + k_frozen);
63
+ #pragma omp parallel
64
+ {
65
+ int nt = omp_get_num_threads();
66
+ int rank = omp_get_thread_num();
67
+
68
+ // this thread is taking care of centroids c0:c1
69
+ size_t c0 = (k * rank) / nt;
70
+ size_t c1 = (k * (rank + 1)) / nt;
71
+ std::vector<float> scratch(d);
72
+
73
+ for (size_t i = 0; i < n; i++) {
74
+ int64_t ci = assign[i];
75
+ if (ci < 0 || ci >= num_centroids) {
76
+ invalid_assignment.store(true, std::memory_order_relaxed);
77
+ continue;
78
+ }
79
+ ci -= k_frozen;
80
+ if (ci >= static_cast<int64_t>(c0) &&
81
+ ci < static_cast<int64_t>(c1)) {
82
+ const float* w = weights ? weights + i : nullptr;
83
+ hassign[ci] += w ? *w : 1.0f;
84
+ add_row(i, w, centroids + ci * d, scratch.data());
85
+ }
86
+ }
87
+ }
88
+ FAISS_THROW_IF_MSG(
89
+ invalid_assignment.load(std::memory_order_relaxed),
90
+ "invalid cluster assignment");
91
+
92
+ #pragma omp parallel for
93
+ for (idx_t ci = 0; ci < static_cast<idx_t>(k); ci++) {
94
+ if (hassign[ci] == 0) {
95
+ continue;
96
+ }
97
+ float norm = 1 / hassign[ci];
98
+ float* c = centroids + ci * d;
99
+ for (size_t j = 0; j < d; j++) {
100
+ c[j] *= norm;
101
+ }
102
+ }
103
+ }
104
+
105
+ } // namespace
106
+
28
107
  uint64_t get_actual_rng_seed(const int seed) {
29
108
  return (seed >= 0)
30
109
  ? seed
@@ -109,66 +188,76 @@ void compute_centroids(
109
188
  const float* weights,
110
189
  float* hassign,
111
190
  float* centroids) {
112
- k -= k_frozen;
113
- centroids += k_frozen * d;
114
-
115
- memset(centroids, 0, sizeof(*centroids) * d * k);
116
-
117
- size_t line_size = codec ? codec->sa_code_size() : d * sizeof(float);
118
-
119
- #pragma omp parallel
120
- {
121
- int nt = omp_get_num_threads();
122
- int rank = omp_get_thread_num();
123
-
124
- // this thread is taking care of centroids c0:c1
125
- size_t c0 = (k * rank) / nt;
126
- size_t c1 = (k * (rank + 1)) / nt;
127
- std::vector<float> decode_buffer(d);
128
-
129
- for (size_t i = 0; i < n; i++) {
130
- int64_t ci = assign[i];
131
- FAISS_THROW_IF_NOT_MSG(
132
- ci >= 0 && ci < k + k_frozen, "invalid cluster assignment");
133
- ci -= k_frozen;
134
- if (ci >= static_cast<int64_t>(c0) &&
135
- ci < static_cast<int64_t>(c1)) {
136
- float* c = centroids + ci * d;
191
+ const size_t line_size = codec ? codec->sa_code_size() : d * sizeof(float);
192
+ compute_centroids_impl(
193
+ d,
194
+ k,
195
+ n,
196
+ k_frozen,
197
+ assign,
198
+ weights,
199
+ hassign,
200
+ centroids,
201
+ [&](size_t i, const float* w, float* c, float* scratch) {
137
202
  const float* xi;
138
203
  if (!codec) {
139
204
  xi = reinterpret_cast<const float*>(x + i * line_size);
140
205
  } else {
141
- float* xif = decode_buffer.data();
142
- codec->sa_decode(1, x + i * line_size, xif);
143
- xi = xif;
206
+ codec->sa_decode(1, x + i * line_size, scratch);
207
+ xi = scratch;
144
208
  }
145
- if (weights) {
146
- float w = weights[i];
147
- hassign[ci] += w;
209
+ if (w) {
210
+ const float wi = *w;
148
211
  for (size_t j = 0; j < d; j++) {
149
- c[j] += xi[j] * w;
212
+ c[j] += xi[j] * wi;
150
213
  }
151
214
  } else {
152
- hassign[ci] += 1.0;
153
215
  for (size_t j = 0; j < d; j++) {
154
216
  c[j] += xi[j];
155
217
  }
156
218
  }
157
- }
158
- }
159
- }
219
+ });
220
+ }
160
221
 
161
- #pragma omp parallel for
162
- for (idx_t ci = 0; ci < static_cast<idx_t>(k); ci++) {
163
- if (hassign[ci] == 0) {
164
- continue;
165
- }
166
- float norm = 1 / hassign[ci];
167
- float* c = centroids + ci * d;
168
- for (size_t j = 0; j < d; j++) {
169
- c[j] *= norm;
222
+ void compute_centroids_fp16(
223
+ size_t d,
224
+ size_t k,
225
+ size_t n,
226
+ size_t k_frozen,
227
+ const uint16_t* x,
228
+ const int64_t* assign,
229
+ const float* weights,
230
+ float* hassign,
231
+ float* centroids) {
232
+ with_selected_simd_levels<AVAILABLE_SIMD_LEVELS_BASE>([&]<SIMDLevel SL>() {
233
+ compute_centroids_impl(
234
+ d,
235
+ k,
236
+ n,
237
+ k_frozen,
238
+ assign,
239
+ weights,
240
+ hassign,
241
+ centroids,
242
+ [&](size_t i, const float* w, float* c, float* /*scratch*/) {
243
+ // x * 1.0f is exact, so unweighted sums match the
244
+ // fp32 path.
245
+ fp16_madd<SL>(d, x + i * d, w ? *w : 1.0f, c);
246
+ });
247
+ });
248
+ }
249
+
250
+ bool fp16_all_finite(size_t n, const uint16_t* x) {
251
+ // binary16 NaNs and infinities are the values with all exponent bits set
252
+ constexpr uint16_t exponent_mask = 0x7c00;
253
+ std::atomic<bool> all_finite{true};
254
+ #pragma omp parallel for if (n > fp16_parallel_threshold)
255
+ for (int64_t i = 0; i < static_cast<int64_t>(n); i++) {
256
+ if ((x[i] & exponent_mask) == exponent_mask) {
257
+ all_finite.store(false, std::memory_order_relaxed);
170
258
  }
171
259
  }
260
+ return all_finite.load(std::memory_order_relaxed);
172
261
  }
173
262
 
174
263
  // a bit above machine epsilon for float16
@@ -51,6 +51,15 @@ idx_t subsample_training_set(
51
51
  uint8_t** x_out,
52
52
  float** weights_out);
53
53
 
54
+ /** Initialize non-random centroids directly from packed IEEE binary16 rows. */
55
+ void init_centroids_fp16(
56
+ const ClusteringInitialization& initializer,
57
+ size_t n,
58
+ const uint16_t* x,
59
+ float* centroids,
60
+ size_t n_existing_centroids = 0,
61
+ const float* existing_centroids = nullptr);
62
+
54
63
  /** compute centroids as (weighted) sum of training points
55
64
  *
56
65
  * @param x training vectors, size n * code_size (from codec)
@@ -75,6 +84,33 @@ void compute_centroids(
75
84
  float* hassign,
76
85
  float* centroids);
77
86
 
87
+ /** Same as compute_centroids for training vectors stored as packed IEEE
88
+ * binary16 values.
89
+ *
90
+ * Rows are widened in registers and accumulated directly into the fp32
91
+ * centroids: there is no per-row decode call and no decode buffer.
92
+ *
93
+ * @param x training vectors, size n * d
94
+ */
95
+ void compute_centroids_fp16(
96
+ size_t d,
97
+ size_t k,
98
+ size_t n,
99
+ size_t k_frozen,
100
+ const uint16_t* x,
101
+ const int64_t* assign,
102
+ const float* weights,
103
+ float* hassign,
104
+ float* centroids);
105
+
106
+ /** Check packed IEEE binary16 values for NaNs and infinities.
107
+ *
108
+ * The check runs on the bit patterns, without widening to fp32.
109
+ *
110
+ * @return true iff none of the `n` values is a NaN or an infinity
111
+ */
112
+ bool fp16_all_finite(size_t n, const uint16_t* x);
113
+
78
114
  /** Handle empty clusters by splitting larger ones.
79
115
  *
80
116
  * It works by slightly changing the centroids to make 2 clusters from