From c670118a4ebbd796dce70ea0a99d32673767f26f Mon Sep 17 00:00:00 2001 From: Matthijs Douze Date: Wed, 22 Feb 2017 23:26:44 +0100 Subject: [PATCH] Initial commit --- .gitignore | 10 + AutoTune.cpp | 776 + AutoTune.h | 213 + AuxIndexStructures.cpp | 206 + AuxIndexStructures.h | 189 + CONTRIBUTING.md | 52 + Clustering.cpp | 217 + Clustering.h | 99 + FaissAssert.h | 26 + Heap.cpp | 133 + Heap.h | 498 + INSTALL | 255 + Index.cpp | 73 + Index.h | 199 + IndexFlat.cpp | 399 + IndexFlat.h | 171 + IndexIVF.cpp | 517 + IndexIVF.h | 187 + IndexIVFPQ.cpp | 1574 + IndexIVFPQ.h | 284 + IndexLSH.cpp | 189 + IndexLSH.h | 86 + IndexPQ.cpp | 886 + IndexPQ.h | 166 + LICENSE | 263 + Makefile | 147 + MetaIndexes.cpp | 625 + MetaIndexes.h | 142 + PolysemousTraining.cpp | 962 + PolysemousTraining.h | 165 + ProductQuantizer.cpp | 665 + ProductQuantizer.h | 175 + README.md | 39 + VectorTransform.cpp | 859 + VectorTransform.h | 291 + benchs/README.md | 243 + docs/Doxyfile | 2281 + docs/html/1-Flat_8cpp_source.html | 199 + docs/html/2-IVFFlat_8cpp_source.html | 183 + docs/html/3-IVFPQ_8cpp_source.html | 195 + docs/html/AutoTune_8cpp_source.html | 946 + docs/html/AutoTune_8h_source.html | 352 + docs/html/AuxIndexStructures_8cpp_source.html | 316 + docs/html/AuxIndexStructures_8h_source.html | 304 + docs/html/BlockSelectFloat128_8cu_source.html | 116 + docs/html/BlockSelectFloat1_8cu_source.html | 116 + docs/html/BlockSelectFloat256_8cu_source.html | 116 + docs/html/BlockSelectFloat32_8cu_source.html | 116 + docs/html/BlockSelectFloat64_8cu_source.html | 116 + .../BlockSelectFloatF1024_8cu_source.html | 115 + .../html/BlockSelectFloatF512_8cu_source.html | 115 + .../BlockSelectFloatT1024_8cu_source.html | 115 + .../html/BlockSelectFloatT512_8cu_source.html | 115 + docs/html/BlockSelectFloat_8cu_source.html | 179 + docs/html/BlockSelectHalf128_8cu_source.html | 118 + docs/html/BlockSelectHalf1_8cu_source.html | 118 + docs/html/BlockSelectHalf256_8cu_source.html | 118 + docs/html/BlockSelectHalf32_8cu_source.html | 118 + docs/html/BlockSelectHalf64_8cu_source.html | 118 + .../html/BlockSelectHalfF1024_8cu_source.html | 117 + docs/html/BlockSelectHalfF512_8cu_source.html | 117 + .../html/BlockSelectHalfT1024_8cu_source.html | 117 + docs/html/BlockSelectHalfT512_8cu_source.html | 117 + docs/html/BlockSelectHalf_8cu_source.html | 183 + docs/html/BlockSelectImpl_8cuh_source.html | 146 + docs/html/BlockSelectKernel_8cuh_source.html | 172 + docs/html/BroadcastSum_8cu_source.html | 464 + docs/html/BroadcastSum_8cuh_source.html | 150 + docs/html/Clustering_8cpp_source.html | 338 + docs/html/Clustering_8h_source.html | 212 + docs/html/Comparators_8cuh_source.html | 152 + docs/html/CompareFlat_8cu_source.html | 244 + docs/html/CompareIVFFlat_8cu_source.html | 255 + docs/html/CompareIVFPQGrid_8cu_source.html | 304 + docs/html/CompareIVFPQ_8cu_source.html | 264 + .../html/ConversionOperators_8cuh_source.html | 171 + docs/html/CopyUtils_8cuh_source.html | 188 + docs/html/DeviceDefs_8cuh_source.html | 160 + docs/html/DeviceMemory_8cpp_source.html | 182 + docs/html/DeviceMemory_8h_source.html | 179 + docs/html/DeviceTensor-inl_8cuh_source.html | 322 + docs/html/DeviceTensor_8cuh_source.html | 214 + docs/html/DeviceUtils_8cpp_source.html | 266 + docs/html/DeviceUtils_8h_source.html | 258 + docs/html/DeviceVector_8cuh_source.html | 275 + docs/html/Distance_8cu_source.html | 439 + docs/html/Distance_8cuh_source.html | 171 + docs/html/FaissAssert_8h_source.html | 120 + docs/html/FlatIndex_8cu_source.html | 349 + docs/html/FlatIndex_8cuh_source.html | 221 + docs/html/Float16_8cu_source.html | 214 + docs/html/Float16_8cuh_source.html | 255 + docs/html/GpuAutoTune_8cpp_source.html | 481 + docs/html/GpuAutoTune_8h_source.html | 193 + docs/html/GpuIndexFlat_8cu_source.html | 688 + docs/html/GpuIndexFlat_8h_source.html | 315 + docs/html/GpuIndexIVFFlat_8cu_source.html | 431 + docs/html/GpuIndexIVFFlat_8h_source.html | 218 + docs/html/GpuIndexIVFPQ_8cu_source.html | 644 + docs/html/GpuIndexIVFPQ_8h_source.html | 268 + docs/html/GpuIndexIVF_8cu_source.html | 433 + docs/html/GpuIndexIVF_8h_source.html | 224 + docs/html/GpuIndex_8cu_source.html | 131 + docs/html/GpuIndex_8h_source.html | 152 + docs/html/GpuIndicesOptions_8h_source.html | 131 + docs/html/GpuResources_8cpp_source.html | 143 + docs/html/GpuResources_8h_source.html | 157 + docs/html/Heap_8cpp_source.html | 233 + docs/html/Heap_8h_source.html | 608 + docs/html/HostTensor-inl_8cuh_source.html | 262 + docs/html/HostTensor_8cuh_source.html | 195 + docs/html/IVFBase_8cu_source.html | 410 + docs/html/IVFBase_8cuh_source.html | 250 + docs/html/IVFFlatScan_8cu_source.html | 1010 + docs/html/IVFFlatScan_8cuh_source.html | 136 + docs/html/IVFFlat_8cu_source.html | 513 + docs/html/IVFFlat_8cuh_source.html | 175 + docs/html/IVFPQ_8cu_source.html | 841 + docs/html/IVFPQ_8cuh_source.html | 254 + docs/html/IVFUtilsSelect1_8cu_source.html | 245 + docs/html/IVFUtilsSelect2_8cu_source.html | 314 + docs/html/IVFUtils_8cu_source.html | 177 + docs/html/IVFUtils_8cuh_source.html | 152 + docs/html/IndexFlat_8cpp_source.html | 537 + docs/html/IndexFlat_8h_source.html | 297 + docs/html/IndexIVFPQ_8cpp_source.html | 1762 + docs/html/IndexIVFPQ_8h_source.html | 440 + docs/html/IndexIVF_8cpp_source.html | 669 + docs/html/IndexIVF_8h_source.html | 321 + docs/html/IndexLSH_8cpp_source.html | 304 + docs/html/IndexLSH_8h_source.html | 199 + docs/html/IndexPQ_8cpp_source.html | 1039 + docs/html/IndexPQ_8h_source.html | 294 + docs/html/IndexProxy_8cpp_source.html | 335 + docs/html/IndexProxy_8h_source.html | 214 + docs/html/IndexWrapper-inl_8h_source.html | 174 + docs/html/IndexWrapper_8h_source.html | 144 + docs/html/Index_8cpp_source.html | 181 + docs/html/Index_8h_source.html | 316 + docs/html/InvertedListAppend_8cu_source.html | 366 + docs/html/InvertedListAppend_8cuh_source.html | 151 + docs/html/L2Norm_8cu_source.html | 356 + docs/html/L2Norm_8cuh_source.html | 129 + docs/html/L2Select_8cu_source.html | 351 + docs/html/L2Select_8cuh_source.html | 133 + docs/html/Limits_8cuh_source.html | 183 + docs/html/LoadStoreOperators_8cuh_source.html | 168 + docs/html/MathOperators_8cuh_source.html | 670 + docs/html/MatrixMult_8cu_source.html | 370 + docs/html/MatrixMult_8cuh_source.html | 165 + docs/html/MergeNetworkBlock_8cuh_source.html | 303 + docs/html/MergeNetworkWarp_8cuh_source.html | 599 + docs/html/MetaIndexes_8cpp_source.html | 750 + docs/html/MetaIndexes_8h_source.html | 261 + docs/html/NoTypeTensor_8cuh_source.html | 228 + docs/html/PQCodeDistances_8cu_source.html | 608 + docs/html/PQCodeDistances_8cuh_source.html | 142 + docs/html/PQCodeLoad_8cuh_source.html | 464 + ...ScanMultiPassNoPrecomputed_8cu_source.html | 711 + ...canMultiPassNoPrecomputed_8cuh_source.html | 144 + ...PQScanMultiPassPrecomputed_8cu_source.html | 661 + ...QScanMultiPassPrecomputed_8cuh_source.html | 142 + docs/html/Pair_8cuh_source.html | 172 + docs/html/PerfClustering_8cpp_source.html | 221 + docs/html/PerfIVFPQAdd_8cpp_source.html | 246 + docs/html/PolysemousTraining_8cpp_source.html | 1095 + docs/html/PolysemousTraining_8h_source.html | 278 + docs/html/ProductQuantizer_8cpp_source.html | 789 + docs/html/ProductQuantizer_8h_source.html | 299 + docs/html/PtxUtils_8cuh_source.html | 186 + docs/html/ReductionOperators_8cuh_source.html | 181 + docs/html/Reductions_8cuh_source.html | 243 + docs/html/RemapIndices_8cpp_source.html | 144 + docs/html/RemapIndices_8h_source.html | 125 + docs/html/Select_8cuh_source.html | 689 + docs/html/StackDeviceMemory_8cpp_source.html | 350 + docs/html/StackDeviceMemory_8h_source.html | 251 + .../StandardGpuResources_8cpp_source.html | 311 + docs/html/StandardGpuResources_8h_source.html | 202 + docs/html/StaticUtils_8h_source.html | 174 + docs/html/Tensor-inl_8cuh_source.html | 747 + docs/html/Tensor_8cuh_source.html | 779 + docs/html/TestGpuIndexFlat_8cpp_source.html | 337 + .../html/TestGpuIndexIVFFlat_8cpp_source.html | 582 + docs/html/TestGpuIndexIVFPQ_8cpp_source.html | 478 + docs/html/TestGpuSelect_8cu_source.html | 288 + docs/html/TestUtils_8cpp_source.html | 379 + docs/html/TestUtils_8h_source.html | 181 + docs/html/ThrustAllocator_8cuh_source.html | 165 + docs/html/Timer_8cpp_source.html | 166 + docs/html/Timer_8h_source.html | 160 + docs/html/Transpose_8cuh_source.html | 247 + docs/html/VectorResidual_8cu_source.html | 198 + docs/html/VectorResidual_8cuh_source.html | 132 + docs/html/VectorTransform_8cpp_source.html | 1025 + docs/html/VectorTransform_8h_source.html | 441 + docs/html/WarpSelectFloat128_8cu_source.html | 116 + docs/html/WarpSelectFloat1_8cu_source.html | 116 + docs/html/WarpSelectFloat256_8cu_source.html | 116 + docs/html/WarpSelectFloat32_8cu_source.html | 116 + docs/html/WarpSelectFloat64_8cu_source.html | 116 + .../html/WarpSelectFloatF1024_8cu_source.html | 115 + docs/html/WarpSelectFloatF512_8cu_source.html | 115 + .../html/WarpSelectFloatT1024_8cu_source.html | 115 + docs/html/WarpSelectFloatT512_8cu_source.html | 115 + docs/html/WarpSelectFloat_8cu_source.html | 179 + docs/html/WarpSelectHalf128_8cu_source.html | 118 + docs/html/WarpSelectHalf1_8cu_source.html | 118 + docs/html/WarpSelectHalf256_8cu_source.html | 118 + docs/html/WarpSelectHalf32_8cu_source.html | 118 + docs/html/WarpSelectHalf64_8cu_source.html | 118 + docs/html/WarpSelectHalfF1024_8cu_source.html | 117 + docs/html/WarpSelectHalfF512_8cu_source.html | 117 + docs/html/WarpSelectHalfT1024_8cu_source.html | 117 + docs/html/WarpSelectHalfT512_8cu_source.html | 117 + docs/html/WarpSelectHalf_8cu_source.html | 183 + docs/html/WarpSelectImpl_8cuh_source.html | 147 + docs/html/WarpSelectKernel_8cuh_source.html | 173 + docs/html/WarpShuffles_8cuh_source.html | 200 + docs/html/WorkerThread_8cpp_source.html | 216 + docs/html/WorkerThread_8h_source.html | 167 + docs/html/WriteIndex_8cpp_source.html | 208 + docs/html/annotated.html | 294 + docs/html/bc_s.png | Bin 0 -> 676 bytes docs/html/bdwn.png | Bin 0 -> 147 bytes docs/html/classes.html | 168 + ...classfaiss_1_1gpu_1_1CpuTimer-members.html | 106 + docs/html/classfaiss_1_1gpu_1_1CpuTimer.html | 129 + ...s_1_1gpu_1_1CublasHandleScope-members.html | 107 + ...lassfaiss_1_1gpu_1_1CublasHandleScope.html | 124 + ...lassfaiss_1_1gpu_1_1CudaEvent-members.html | 113 + docs/html/classfaiss_1_1gpu_1_1CudaEvent.html | 142 + ...sfaiss_1_1gpu_1_1DeviceMemory-members.html | 112 + .../classfaiss_1_1gpu_1_1DeviceMemory.html | 224 + .../classfaiss_1_1gpu_1_1DeviceMemory.png | Bin 0 -> 813 bytes ...pu_1_1DeviceMemoryReservation-members.html | 113 + ...iss_1_1gpu_1_1DeviceMemoryReservation.html | 136 + ...ssfaiss_1_1gpu_1_1DeviceScope-members.html | 106 + .../classfaiss_1_1gpu_1_1DeviceScope.html | 121 + ...sfaiss_1_1gpu_1_1DeviceTensor-members.html | 171 + .../classfaiss_1_1gpu_1_1DeviceTensor.html | 560 + .../classfaiss_1_1gpu_1_1DeviceTensor.png | Bin 0 -> 1286 bytes ...sfaiss_1_1gpu_1_1DeviceVector-members.html | 116 + .../classfaiss_1_1gpu_1_1DeviceVector.html | 149 + ...lassfaiss_1_1gpu_1_1FlatIndex-members.html | 114 + docs/html/classfaiss_1_1gpu_1_1FlatIndex.html | 206 + ...classfaiss_1_1gpu_1_1GpuIndex-members.html | 132 + docs/html/classfaiss_1_1gpu_1_1GpuIndex.html | 265 + docs/html/classfaiss_1_1gpu_1_1GpuIndex.png | Bin 0 -> 2504 bytes ...sfaiss_1_1gpu_1_1GpuIndexFlat-members.html | 147 + .../classfaiss_1_1gpu_1_1GpuIndexFlat.html | 563 + .../classfaiss_1_1gpu_1_1GpuIndexFlat.png | Bin 0 -> 1648 bytes ...aiss_1_1gpu_1_1GpuIndexFlatIP-members.html | 151 + .../classfaiss_1_1gpu_1_1GpuIndexFlatIP.html | 358 + .../classfaiss_1_1gpu_1_1GpuIndexFlatIP.png | Bin 0 -> 1217 bytes ...aiss_1_1gpu_1_1GpuIndexFlatL2-members.html | 151 + .../classfaiss_1_1gpu_1_1GpuIndexFlatL2.html | 358 + .../classfaiss_1_1gpu_1_1GpuIndexFlatL2.png | Bin 0 -> 1232 bytes ...ssfaiss_1_1gpu_1_1GpuIndexIVF-members.html | 150 + .../classfaiss_1_1gpu_1_1GpuIndexIVF.html | 348 + .../html/classfaiss_1_1gpu_1_1GpuIndexIVF.png | Bin 0 -> 1639 bytes ...iss_1_1gpu_1_1GpuIndexIVFFlat-members.html | 157 + .../classfaiss_1_1gpu_1_1GpuIndexIVFFlat.html | 631 + .../classfaiss_1_1gpu_1_1GpuIndexIVFFlat.png | Bin 0 -> 1233 bytes ...faiss_1_1gpu_1_1GpuIndexIVFPQ-members.html | 166 + .../classfaiss_1_1gpu_1_1GpuIndexIVFPQ.html | 666 + .../classfaiss_1_1gpu_1_1GpuIndexIVFPQ.png | Bin 0 -> 1222 bytes ...sfaiss_1_1gpu_1_1GpuResources-members.html | 117 + .../classfaiss_1_1gpu_1_1GpuResources.html | 190 + .../classfaiss_1_1gpu_1_1GpuResources.png | Bin 0 -> 819 bytes ..._1GpuResourcesThrustAllocator-members.html | 109 + ...1_1gpu_1_1GpuResourcesThrustAllocator.html | 134 + ...assfaiss_1_1gpu_1_1HostTensor-members.html | 168 + .../html/classfaiss_1_1gpu_1_1HostTensor.html | 507 + docs/html/classfaiss_1_1gpu_1_1HostTensor.png | Bin 0 -> 1258 bytes .../classfaiss_1_1gpu_1_1IVFBase-members.html | 130 + docs/html/classfaiss_1_1gpu_1_1IVFBase.html | 499 + docs/html/classfaiss_1_1gpu_1_1IVFBase.png | Bin 0 -> 773 bytes .../classfaiss_1_1gpu_1_1IVFFlat-members.html | 136 + docs/html/classfaiss_1_1gpu_1_1IVFFlat.html | 395 + docs/html/classfaiss_1_1gpu_1_1IVFFlat.png | Bin 0 -> 581 bytes .../classfaiss_1_1gpu_1_1IVFPQ-members.html | 140 + docs/html/classfaiss_1_1gpu_1_1IVFPQ.html | 469 + docs/html/classfaiss_1_1gpu_1_1IVFPQ.png | Bin 0 -> 586 bytes ...assfaiss_1_1gpu_1_1IndexProxy-members.html | 136 + .../html/classfaiss_1_1gpu_1_1IndexProxy.html | 437 + docs/html/classfaiss_1_1gpu_1_1IndexProxy.png | Bin 0 -> 570 bytes ...ssfaiss_1_1gpu_1_1KernelTimer-members.html | 107 + .../classfaiss_1_1gpu_1_1KernelTimer.html | 168 + ...sfaiss_1_1gpu_1_1NoTypeTensor-members.html | 114 + .../classfaiss_1_1gpu_1_1NoTypeTensor.html | 146 + ...s_1_1gpu_1_1StackDeviceMemory-members.html | 116 + ...lassfaiss_1_1gpu_1_1StackDeviceMemory.html | 286 + ...classfaiss_1_1gpu_1_1StackDeviceMemory.png | Bin 0 -> 812 bytes ..._1gpu_1_1StandardGpuResources-members.html | 123 + ...sfaiss_1_1gpu_1_1StandardGpuResources.html | 249 + ...ssfaiss_1_1gpu_1_1StandardGpuResources.png | Bin 0 -> 816 bytes .../classfaiss_1_1gpu_1_1Tensor-members.html | 157 + docs/html/classfaiss_1_1gpu_1_1Tensor.html | 928 + docs/html/classfaiss_1_1gpu_1_1Tensor.png | Bin 0 -> 1729 bytes ...sfaiss_1_1gpu_1_1WorkerThread-members.html | 109 + .../classfaiss_1_1gpu_1_1WorkerThread.html | 182 + ...1_1gpu_1_1detail_1_1SubTensor-members.html | 122 + ...ssfaiss_1_1gpu_1_1detail_1_1SubTensor.html | 302 + ...pe_00_010_00_01PtrTraits_01_4-members.html | 122 + ...TensorType_00_010_00_01PtrTraits_01_4.html | 211 + docs/html/closed.png | Bin 0 -> 132 bytes docs/html/demo__imi__flat_8cpp_source.html | 255 + .../demo__ivfpq__indexing_8cpp_source.html | 249 + ...emo__ivfpq__indexing__gpu_8cpp_source.html | 264 + docs/html/demo__sift1M_8cpp_source.html | 367 + .../dir_02dc1cf4df13911cc8cfd20f07047b71.html | 115 + .../dir_07c2df013bb20677b8e65a9f18968d2c.html | 99 + .../dir_29631f2f8051359899a6ac5f9629d2be.html | 103 + .../dir_43be364029218a94c5bcb5d8c78d7156.html | 111 + .../dir_498271007b03b2a0521055e88776887b.html | 196 + .../dir_49d1182a3b8dfb62757c53ae905481ad.html | 167 + .../dir_59425e443f801f1f2fd8bbe4959a3ccf.html | 107 + .../dir_6b3ae6988449b0834e9596fad5d75199.html | 146 + .../dir_d92646511ea2c8ec2c89d5c37e8b5981.html | 135 + .../dir_dbd6ec420472444c618282e2c8178be7.html | 135 + docs/html/doxygen.css | 1357 + docs/html/doxygen.png | Bin 0 -> 3779 bytes docs/html/dynsections.js | 104 + docs/html/faiss_8h_source.html | 140 + docs/html/files.html | 306 + docs/html/ftv2blank.png | Bin 0 -> 86 bytes docs/html/ftv2cl.png | Bin 0 -> 453 bytes docs/html/ftv2doc.png | Bin 0 -> 746 bytes docs/html/ftv2folderclosed.png | Bin 0 -> 616 bytes docs/html/ftv2folderopen.png | Bin 0 -> 597 bytes docs/html/ftv2lastnode.png | Bin 0 -> 86 bytes docs/html/ftv2link.png | Bin 0 -> 746 bytes docs/html/ftv2mlastnode.png | Bin 0 -> 246 bytes docs/html/ftv2mnode.png | Bin 0 -> 246 bytes docs/html/ftv2mo.png | Bin 0 -> 403 bytes docs/html/ftv2node.png | Bin 0 -> 86 bytes docs/html/ftv2ns.png | Bin 0 -> 388 bytes docs/html/ftv2plastnode.png | Bin 0 -> 229 bytes docs/html/ftv2pnode.png | Bin 0 -> 229 bytes docs/html/ftv2splitbar.png | Bin 0 -> 314 bytes docs/html/ftv2vertline.png | Bin 0 -> 86 bytes docs/html/functions.html | 245 + docs/html/functions_0x62.html | 164 + docs/html/functions_0x63.html | 263 + docs/html/functions_0x64.html | 215 + docs/html/functions_0x65.html | 163 + docs/html/functions_0x66.html | 140 + docs/html/functions_0x67.html | 274 + docs/html/functions_0x68.html | 161 + docs/html/functions_0x69.html | 223 + docs/html/functions_0x6b.html | 157 + docs/html/functions_0x6c.html | 163 + docs/html/functions_0x6d.html | 200 + docs/html/functions_0x6e.html | 209 + docs/html/functions_0x6f.html | 182 + docs/html/functions_0x70.html | 176 + docs/html/functions_0x71.html | 151 + docs/html/functions_0x72.html | 274 + docs/html/functions_0x73.html | 298 + docs/html/functions_0x74.html | 229 + docs/html/functions_0x75.html | 182 + docs/html/functions_0x76.html | 156 + docs/html/functions_0x77.html | 150 + docs/html/functions_0x78.html | 140 + docs/html/functions_0x7a.html | 141 + docs/html/functions_0x7e.html | 149 + docs/html/functions_enum.html | 118 + docs/html/functions_eval.html | 151 + docs/html/functions_func.html | 224 + docs/html/functions_func_0x63.html | 230 + docs/html/functions_func_0x64.html | 168 + docs/html/functions_func_0x65.html | 155 + docs/html/functions_func_0x66.html | 138 + docs/html/functions_func_0x67.html | 263 + docs/html/functions_func_0x68.html | 147 + docs/html/functions_func_0x69.html | 191 + docs/html/functions_func_0x6b.html | 138 + docs/html/functions_func_0x6c.html | 146 + docs/html/functions_func_0x6d.html | 160 + docs/html/functions_func_0x6e.html | 153 + docs/html/functions_func_0x6f.html | 155 + docs/html/functions_func_0x70.html | 147 + docs/html/functions_func_0x71.html | 139 + docs/html/functions_func_0x72.html | 247 + docs/html/functions_func_0x73.html | 238 + docs/html/functions_func_0x74.html | 184 + docs/html/functions_func_0x75.html | 159 + docs/html/functions_func_0x76.html | 142 + docs/html/functions_func_0x77.html | 142 + docs/html/functions_func_0x7a.html | 139 + docs/html/functions_func_0x7e.html | 147 + docs/html/functions_rela.html | 118 + docs/html/functions_type.html | 112 + docs/html/functions_vars.html | 672 + docs/html/hamming_8cpp_source.html | 781 + docs/html/hamming_8h_source.html | 538 + docs/html/hierarchy.html | 299 + docs/html/index.html | 89 + docs/html/index__io_8cpp_source.html | 809 + docs/html/index__io_8h_source.html | 160 + docs/html/jquery.js | 39 + docs/html/namespacefaiss.html | 1842 + .../html/namespacefaiss_1_1gpu_1_1detail.html | 116 + docs/html/namespacemembers.html | 243 + docs/html/namespacemembers_enum.html | 103 + docs/html/namespacemembers_func.html | 240 + docs/html/namespaces.html | 102 + docs/html/nav_f.png | Bin 0 -> 153 bytes docs/html/nav_g.png | Bin 0 -> 108 bytes docs/html/nav_h.png | Bin 0 -> 98 bytes docs/html/open.png | Bin 0 -> 123 bytes docs/html/search/all_61.html | 26 + docs/html/search/all_61.js | 29 + docs/html/search/all_62.html | 26 + docs/html/search/all_62.js | 28 + docs/html/search/all_63.html | 26 + docs/html/search/all_63.js | 57 + docs/html/search/all_64.html | 26 + docs/html/search/all_64.js | 34 + docs/html/search/all_65.html | 26 + docs/html/search/all_65.js | 11 + docs/html/search/all_66.html | 26 + docs/html/search/all_66.js | 18 + docs/html/search/all_67.html | 26 + docs/html/search/all_67.js | 64 + docs/html/search/all_68.html | 26 + docs/html/search/all_68.js | 26 + docs/html/search/all_69.html | 26 + docs/html/search/all_69.js | 70 + docs/html/search/all_6b.html | 26 + docs/html/search/all_6b.js | 14 + docs/html/search/all_6c.html | 26 + docs/html/search/all_6c.js | 34 + docs/html/search/all_6d.html | 26 + docs/html/search/all_6d.js | 31 + docs/html/search/all_6e.html | 26 + docs/html/search/all_6e.js | 27 + docs/html/search/all_6f.html | 26 + docs/html/search/all_6f.js | 21 + docs/html/search/all_70.html | 26 + docs/html/search/all_70.js | 24 + docs/html/search/all_71.html | 26 + docs/html/search/all_71.js | 8 + docs/html/search/all_72.html | 26 + docs/html/search/all_72.js | 46 + docs/html/search/all_73.html | 26 + docs/html/search/all_73.js | 62 + docs/html/search/all_74.html | 26 + docs/html/search/all_74.js | 34 + docs/html/search/all_75.html | 26 + docs/html/search/all_75.js | 18 + docs/html/search/all_76.html | 26 + docs/html/search/all_76.js | 9 + docs/html/search/all_77.html | 26 + docs/html/search/all_77.js | 10 + docs/html/search/all_78.html | 26 + docs/html/search/all_78.js | 4 + docs/html/search/all_7a.html | 26 + docs/html/search/all_7a.js | 4 + docs/html/search/all_7e.html | 26 + docs/html/search/all_7e.js | 7 + docs/html/search/classes_61.html | 26 + docs/html/search/classes_61.js | 5 + docs/html/search/classes_62.html | 26 + docs/html/search/classes_62.js | 18 + docs/html/search/classes_63.html | 26 + docs/html/search/classes_63.js | 21 + docs/html/search/classes_64.html | 26 + docs/html/search/classes_64.js | 14 + docs/html/search/classes_66.html | 26 + docs/html/search/classes_66.js | 9 + docs/html/search/classes_67.html | 26 + docs/html/search/classes_67.js | 19 + docs/html/search/classes_68.html | 26 + docs/html/search/classes_68.js | 14 + docs/html/search/classes_69.html | 26 + docs/html/search/classes_69.js | 40 + docs/html/search/classes_6b.html | 26 + docs/html/search/classes_6b.js | 4 + docs/html/search/classes_6c.html | 26 + docs/html/search/classes_6c.js | 26 + docs/html/search/classes_6d.html | 26 + docs/html/search/classes_6d.js | 10 + docs/html/search/classes_6e.html | 26 + docs/html/search/classes_6e.js | 5 + docs/html/search/classes_6f.html | 26 + docs/html/search/classes_6f.js | 8 + docs/html/search/classes_70.html | 26 + docs/html/search/classes_70.js | 10 + docs/html/search/classes_71.html | 26 + docs/html/search/classes_71.js | 4 + docs/html/search/classes_72.html | 26 + docs/html/search/classes_72.js | 12 + docs/html/search/classes_73.html | 26 + docs/html/search/classes_73.js | 16 + docs/html/search/classes_74.html | 26 + docs/html/search/classes_74.js | 11 + docs/html/search/classes_76.html | 26 + docs/html/search/classes_76.js | 4 + docs/html/search/classes_77.html | 26 + docs/html/search/classes_77.js | 6 + docs/html/search/close.png | Bin 0 -> 273 bytes docs/html/search/enums_61.html | 26 + docs/html/search/enums_61.js | 4 + docs/html/search/enums_6d.html | 26 + docs/html/search/enums_6d.js | 4 + docs/html/search/enums_6f.html | 26 + docs/html/search/enums_6f.js | 4 + docs/html/search/enums_73.html | 26 + docs/html/search/enums_73.js | 4 + docs/html/search/enums_74.html | 26 + docs/html/search/enums_74.js | 4 + docs/html/search/enumvalues_61.html | 26 + docs/html/search/enumvalues_61.js | 6 + docs/html/search/enumvalues_6f.html | 26 + docs/html/search/enumvalues_6f.js | 5 + docs/html/search/enumvalues_73.html | 26 + docs/html/search/enumvalues_73.js | 9 + docs/html/search/enumvalues_74.html | 26 + docs/html/search/enumvalues_74.js | 7 + docs/html/search/functions_61.html | 26 + docs/html/search/functions_61.js | 21 + docs/html/search/functions_62.html | 26 + docs/html/search/functions_62.js | 4 + docs/html/search/functions_63.html | 26 + docs/html/search/functions_63.js | 30 + docs/html/search/functions_64.html | 26 + docs/html/search/functions_64.js | 11 + docs/html/search/functions_65.html | 26 + docs/html/search/functions_65.js | 9 + docs/html/search/functions_66.html | 26 + docs/html/search/functions_66.js | 10 + docs/html/search/functions_67.html | 26 + docs/html/search/functions_67.js | 45 + docs/html/search/functions_68.html | 26 + docs/html/search/functions_68.js | 11 + docs/html/search/functions_69.html | 26 + docs/html/search/functions_69.js | 24 + docs/html/search/functions_6b.html | 26 + docs/html/search/functions_6b.js | 9 + docs/html/search/functions_6c.html | 26 + docs/html/search/functions_6c.js | 6 + docs/html/search/functions_6d.html | 26 + docs/html/search/functions_6d.js | 11 + docs/html/search/functions_6e.html | 26 + docs/html/search/functions_6e.js | 9 + docs/html/search/functions_6f.html | 26 + docs/html/search/functions_6f.js | 9 + docs/html/search/functions_70.html | 26 + docs/html/search/functions_70.js | 9 + docs/html/search/functions_71.html | 26 + docs/html/search/functions_71.js | 4 + docs/html/search/functions_72.html | 26 + docs/html/search/functions_72.js | 29 + docs/html/search/functions_73.html | 26 + docs/html/search/functions_73.js | 30 + docs/html/search/functions_74.html | 26 + docs/html/search/functions_74.js | 12 + docs/html/search/functions_75.html | 26 + docs/html/search/functions_75.js | 11 + docs/html/search/functions_76.html | 26 + docs/html/search/functions_76.js | 5 + docs/html/search/functions_77.html | 26 + docs/html/search/functions_77.js | 5 + docs/html/search/functions_7a.html | 26 + docs/html/search/functions_7a.js | 4 + docs/html/search/functions_7e.html | 26 + docs/html/search/functions_7e.js | 7 + docs/html/search/mag_sel.png | Bin 0 -> 563 bytes docs/html/search/namespaces_66.html | 26 + docs/html/search/namespaces_66.js | 5 + docs/html/search/nomatches.html | 12 + docs/html/search/related_73.html | 26 + docs/html/search/related_73.js | 5 + docs/html/search/related_74.html | 26 + docs/html/search/related_74.js | 5 + docs/html/search/search.css | 271 + docs/html/search/search.js | 811 + docs/html/search/search_l.png | Bin 0 -> 604 bytes docs/html/search/search_m.png | Bin 0 -> 158 bytes docs/html/search/search_r.png | Bin 0 -> 612 bytes docs/html/search/typedefs_68.html | 26 + docs/html/search/typedefs_68.js | 4 + docs/html/search/typedefs_69.html | 26 + docs/html/search/typedefs_69.js | 4 + docs/html/search/variables_61.html | 26 + docs/html/search/variables_61.js | 5 + docs/html/search/variables_62.html | 26 + docs/html/search/variables_62.js | 12 + docs/html/search/variables_63.html | 26 + docs/html/search/variables_63.js | 12 + docs/html/search/variables_64.html | 26 + docs/html/search/variables_64.js | 15 + docs/html/search/variables_65.html | 26 + docs/html/search/variables_65.js | 5 + docs/html/search/variables_67.html | 26 + docs/html/search/variables_67.js | 6 + docs/html/search/variables_68.html | 26 + docs/html/search/variables_68.js | 6 + docs/html/search/variables_69.html | 26 + docs/html/search/variables_69.js | 11 + docs/html/search/variables_6b.html | 26 + docs/html/search/variables_6b.js | 7 + docs/html/search/variables_6c.html | 26 + docs/html/search/variables_6c.js | 8 + docs/html/search/variables_6d.html | 26 + docs/html/search/variables_6d.js | 15 + docs/html/search/variables_6e.html | 26 + docs/html/search/variables_6e.js | 19 + docs/html/search/variables_6f.html | 26 + docs/html/search/variables_6f.js | 7 + docs/html/search/variables_70.html | 26 + docs/html/search/variables_70.js | 11 + docs/html/search/variables_71.html | 26 + docs/html/search/variables_71.js | 6 + docs/html/search/variables_72.html | 26 + docs/html/search/variables_72.js | 11 + docs/html/search/variables_73.html | 26 + docs/html/search/variables_73.js | 13 + docs/html/search/variables_74.html | 26 + docs/html/search/variables_74.js | 10 + docs/html/search/variables_75.html | 26 + docs/html/search/variables_75.js | 10 + docs/html/search/variables_76.html | 26 + docs/html/search/variables_76.js | 6 + docs/html/search/variables_77.html | 26 + docs/html/search/variables_77.js | 5 + docs/html/search/variables_78.html | 26 + docs/html/search/variables_78.js | 4 + docs/html/structOptions-members.html | 120 + docs/html/structOptions.html | 169 + docs/html/structfaiss_1_1ArgSort-members.html | 106 + docs/html/structfaiss_1_1ArgSort.html | 127 + ...uctfaiss_1_1AutoTuneCriterion-members.html | 114 + .../structfaiss_1_1AutoTuneCriterion.html | 254 + .../html/structfaiss_1_1AutoTuneCriterion.png | Bin 0 -> 1003 bytes ..._1BaseShiftDistanceCorrection-members.html | 106 + ...tfaiss_1_1BaseShiftDistanceCorrection.html | 124 + .../structfaiss_1_1BufferList-members.html | 113 + docs/html/structfaiss_1_1BufferList.html | 207 + docs/html/structfaiss_1_1BufferList.png | Bin 0 -> 722 bytes ...faiss_1_1BufferList_1_1Buffer-members.html | 106 + .../structfaiss_1_1BufferList_1_1Buffer.html | 120 + docs/html/structfaiss_1_1CMax-members.html | 109 + docs/html/structfaiss_1_1CMax.html | 139 + docs/html/structfaiss_1_1CMin-members.html | 109 + docs/html/structfaiss_1_1CMin.html | 136 + docs/html/structfaiss_1_1Cloner-members.html | 108 + docs/html/structfaiss_1_1Cloner.html | 138 + docs/html/structfaiss_1_1Cloner.png | Bin 0 -> 1184 bytes .../structfaiss_1_1Clustering-members.html | 122 + docs/html/structfaiss_1_1Clustering.html | 219 + docs/html/structfaiss_1_1Clustering.png | Bin 0 -> 690 bytes ...faiss_1_1ClusteringParameters-members.html | 113 + .../structfaiss_1_1ClusteringParameters.html | 166 + .../structfaiss_1_1ClusteringParameters.png | Bin 0 -> 687 bytes docs/html/structfaiss_1_1CodeCmp-members.html | 108 + docs/html/structfaiss_1_1CodeCmp.html | 130 + ...faiss_1_1GenHammingComputer16-members.html | 108 + .../structfaiss_1_1GenHammingComputer16.html | 130 + ...faiss_1_1GenHammingComputer32-members.html | 110 + .../structfaiss_1_1GenHammingComputer32.html | 136 + ...tfaiss_1_1GenHammingComputer8-members.html | 107 + .../structfaiss_1_1GenHammingComputer8.html | 127 + ...faiss_1_1GenHammingComputerM8-members.html | 108 + .../structfaiss_1_1GenHammingComputerM8.html | 130 + ...tructfaiss_1_1HammingComputer-members.html | 109 + docs/html/structfaiss_1_1HammingComputer.html | 146 + docs/html/structfaiss_1_1HammingComputer.png | Bin 0 -> 840 bytes ...uctfaiss_1_1HammingComputer16-members.html | 108 + .../structfaiss_1_1HammingComputer16.html | 130 + ...uctfaiss_1_1HammingComputer20-members.html | 109 + .../structfaiss_1_1HammingComputer20.html | 133 + ...uctfaiss_1_1HammingComputer32-members.html | 110 + .../structfaiss_1_1HammingComputer32.html | 136 + ...ructfaiss_1_1HammingComputer4-members.html | 107 + .../html/structfaiss_1_1HammingComputer4.html | 127 + ...uctfaiss_1_1HammingComputer64-members.html | 114 + .../structfaiss_1_1HammingComputer64.html | 148 + ...ructfaiss_1_1HammingComputer8-members.html | 107 + .../html/structfaiss_1_1HammingComputer8.html | 127 + ...uctfaiss_1_1HammingComputerM4-members.html | 108 + .../structfaiss_1_1HammingComputerM4.html | 130 + ...uctfaiss_1_1HammingComputerM8-members.html | 108 + .../structfaiss_1_1HammingComputerM8.html | 139 + .../html/structfaiss_1_1HammingComputerM8.png | Bin 0 -> 844 bytes .../structfaiss_1_1HeapArray-members.html | 117 + docs/html/structfaiss_1_1HeapArray.html | 333 + .../structfaiss_1_1IDSelector-members.html | 107 + docs/html/structfaiss_1_1IDSelector.html | 137 + docs/html/structfaiss_1_1IDSelector.png | Bin 0 -> 843 bytes ...tructfaiss_1_1IDSelectorBatch-members.html | 114 + docs/html/structfaiss_1_1IDSelectorBatch.html | 160 + docs/html/structfaiss_1_1IDSelectorBatch.png | Bin 0 -> 596 bytes ...tructfaiss_1_1IDSelectorRange-members.html | 111 + docs/html/structfaiss_1_1IDSelectorRange.html | 150 + docs/html/structfaiss_1_1IDSelectorRange.png | Bin 0 -> 613 bytes docs/html/structfaiss_1_1Index-members.html | 127 + docs/html/structfaiss_1_1Index.html | 741 + docs/html/structfaiss_1_1Index.png | Bin 0 -> 4905 bytes .../structfaiss_1_1IndexFlat-members.html | 131 + docs/html/structfaiss_1_1IndexFlat.html | 490 + docs/html/structfaiss_1_1IndexFlat.png | Bin 0 -> 1730 bytes .../structfaiss_1_1IndexFlat1D-members.html | 137 + docs/html/structfaiss_1_1IndexFlat1D.html | 362 + docs/html/structfaiss_1_1IndexFlat1D.png | Bin 0 -> 944 bytes .../structfaiss_1_1IndexFlatIP-members.html | 133 + docs/html/structfaiss_1_1IndexFlatIP.html | 205 + docs/html/structfaiss_1_1IndexFlatIP.png | Bin 0 -> 713 bytes .../structfaiss_1_1IndexFlatL2-members.html | 133 + docs/html/structfaiss_1_1IndexFlatL2.html | 207 + docs/html/structfaiss_1_1IndexFlatL2.png | Bin 0 -> 1572 bytes ...faiss_1_1IndexFlatL2BaseShift-members.html | 135 + .../structfaiss_1_1IndexFlatL2BaseShift.html | 285 + .../structfaiss_1_1IndexFlatL2BaseShift.png | Bin 0 -> 1152 bytes .../structfaiss_1_1IndexIDMap-members.html | 132 + docs/html/structfaiss_1_1IndexIDMap.html | 378 + docs/html/structfaiss_1_1IndexIDMap.png | Bin 0 -> 521 bytes .../html/structfaiss_1_1IndexIVF-members.html | 145 + docs/html/structfaiss_1_1IndexIVF.html | 410 + docs/html/structfaiss_1_1IndexIVF.png | Bin 0 -> 1702 bytes .../structfaiss_1_1IndexIVFFlat-members.html | 152 + docs/html/structfaiss_1_1IndexIVFFlat.html | 598 + docs/html/structfaiss_1_1IndexIVFFlat.png | Bin 0 -> 721 bytes .../structfaiss_1_1IndexIVFPQ-members.html | 166 + docs/html/structfaiss_1_1IndexIVFPQ.html | 838 + docs/html/structfaiss_1_1IndexIVFPQ.png | Bin 0 -> 1553 bytes ...uctfaiss_1_1IndexIVFPQCompact-members.html | 179 + .../structfaiss_1_1IndexIVFPQCompact.html | 468 + .../html/structfaiss_1_1IndexIVFPQCompact.png | Bin 0 -> 1140 bytes .../structfaiss_1_1IndexIVFPQR-members.html | 172 + docs/html/structfaiss_1_1IndexIVFPQR.html | 554 + docs/html/structfaiss_1_1IndexIVFPQR.png | Bin 0 -> 968 bytes ...tructfaiss_1_1IndexIVFPQStats-members.html | 118 + docs/html/structfaiss_1_1IndexIVFPQStats.html | 161 + .../html/structfaiss_1_1IndexLSH-members.html | 140 + docs/html/structfaiss_1_1IndexLSH.html | 451 + docs/html/structfaiss_1_1IndexLSH.png | Bin 0 -> 487 bytes docs/html/structfaiss_1_1IndexPQ-members.html | 146 + docs/html/structfaiss_1_1IndexPQ.html | 695 + docs/html/structfaiss_1_1IndexPQ.png | Bin 0 -> 476 bytes .../structfaiss_1_1IndexPQStats-members.html | 109 + docs/html/structfaiss_1_1IndexPQStats.html | 134 + ...uctfaiss_1_1IndexPreTransform-members.html | 136 + .../structfaiss_1_1IndexPreTransform.html | 543 + .../html/structfaiss_1_1IndexPreTransform.png | Bin 0 -> 603 bytes ...tructfaiss_1_1IndexRefineFlat-members.html | 134 + docs/html/structfaiss_1_1IndexRefineFlat.html | 390 + docs/html/structfaiss_1_1IndexRefineFlat.png | Bin 0 -> 528 bytes .../structfaiss_1_1IndexShards-members.html | 136 + docs/html/structfaiss_1_1IndexShards.html | 438 + docs/html/structfaiss_1_1IndexShards.png | Bin 0 -> 520 bytes ...uctfaiss_1_1IndexSplitVectors-members.html | 135 + .../structfaiss_1_1IndexSplitVectors.html | 379 + .../html/structfaiss_1_1IndexSplitVectors.png | Bin 0 -> 597 bytes .../structfaiss_1_1IndirectSort-members.html | 106 + docs/html/structfaiss_1_1IndirectSort.html | 124 + ...aiss_1_1IntersectionCriterion-members.html | 117 + .../structfaiss_1_1IntersectionCriterion.html | 218 + .../structfaiss_1_1IntersectionCriterion.png | Bin 0 -> 671 bytes ...tructfaiss_1_1LinearTransform-members.html | 123 + docs/html/structfaiss_1_1LinearTransform.html | 245 + docs/html/structfaiss_1_1LinearTransform.png | Bin 0 -> 1651 bytes docs/html/structfaiss_1_1MinSumK-members.html | 121 + docs/html/structfaiss_1_1MinSumK.html | 193 + ...tfaiss_1_1MultiIndexQuantizer-members.html | 130 + .../structfaiss_1_1MultiIndexQuantizer.html | 411 + .../structfaiss_1_1MultiIndexQuantizer.png | Bin 0 -> 611 bytes ...aiss_1_1NopDistanceCorrection-members.html | 105 + .../structfaiss_1_1NopDistanceCorrection.html | 117 + .../structfaiss_1_1OPQMatrix-members.html | 128 + docs/html/structfaiss_1_1OPQMatrix.html | 325 + docs/html/structfaiss_1_1OPQMatrix.png | Bin 0 -> 890 bytes ...aiss_1_1OneRecallAtRCriterion-members.html | 118 + .../structfaiss_1_1OneRecallAtRCriterion.html | 173 + .../structfaiss_1_1OneRecallAtRCriterion.png | Bin 0 -> 720 bytes ...structfaiss_1_1OperatingPoint-members.html | 108 + docs/html/structfaiss_1_1OperatingPoint.html | 133 + ...tructfaiss_1_1OperatingPoints-members.html | 114 + docs/html/structfaiss_1_1OperatingPoints.html | 213 + .../structfaiss_1_1PCAMatrix-members.html | 132 + docs/html/structfaiss_1_1PCAMatrix.html | 333 + docs/html/structfaiss_1_1PCAMatrix.png | Bin 0 -> 892 bytes ...structfaiss_1_1ParameterRange-members.html | 106 + docs/html/structfaiss_1_1ParameterRange.html | 126 + ...structfaiss_1_1ParameterSpace-members.html | 122 + docs/html/structfaiss_1_1ParameterSpace.html | 319 + docs/html/structfaiss_1_1ParameterSpace.png | Bin 0 -> 770 bytes ...faiss_1_1PermutationObjective-members.html | 108 + .../structfaiss_1_1PermutationObjective.html | 146 + .../structfaiss_1_1PermutationObjective.png | Bin 0 -> 2200 bytes ...ctfaiss_1_1PolysemousTraining-members.html | 125 + .../structfaiss_1_1PolysemousTraining.html | 249 + .../structfaiss_1_1PolysemousTraining.png | Bin 0 -> 838 bytes ...ructfaiss_1_1ProductQuantizer-members.html | 142 + .../html/structfaiss_1_1ProductQuantizer.html | 502 + ...tructfaiss_1_1RandomGenerator-members.html | 111 + docs/html/structfaiss_1_1RandomGenerator.html | 174 + ...faiss_1_1RandomRotationMatrix-members.html | 126 + .../structfaiss_1_1RandomRotationMatrix.html | 244 + .../structfaiss_1_1RandomRotationMatrix.png | Bin 0 -> 974 bytes ...s_1_1RangeSearchPartialResult-members.html | 120 + ...ructfaiss_1_1RangeSearchPartialResult.html | 195 + ...tructfaiss_1_1RangeSearchPartialResult.png | Bin 0 -> 715 bytes ...hPartialResult_1_1QueryResult-members.html | 108 + ...ngeSearchPartialResult_1_1QueryResult.html | 136 + ...uctfaiss_1_1RangeSearchResult-members.html | 113 + .../structfaiss_1_1RangeSearchResult.html | 182 + .../structfaiss_1_1RankingScore2-members.html | 128 + docs/html/structfaiss_1_1RankingScore2.html | 234 + docs/html/structfaiss_1_1RankingScore2.png | Bin 0 -> 1181 bytes ...s_1_1RemapDimensionsTransform-members.html | 118 + ...ructfaiss_1_1RemapDimensionsTransform.html | 224 + ...tructfaiss_1_1RemapDimensionsTransform.png | Bin 0 -> 738 bytes ..._1ReproduceDistancesObjective-members.html | 119 + ...tfaiss_1_1ReproduceDistancesObjective.html | 175 + ...ctfaiss_1_1ReproduceDistancesObjective.png | Bin 0 -> 832 bytes ...structfaiss_1_1Score3Computer-members.html | 118 + docs/html/structfaiss_1_1Score3Computer.html | 275 + docs/html/structfaiss_1_1Score3Computer.png | Bin 0 -> 855 bytes ...tructfaiss_1_1SemiSortedArray-members.html | 117 + docs/html/structfaiss_1_1SemiSortedArray.html | 166 + ..._1SimulatedAnnealingOptimizer-members.html | 122 + ...tfaiss_1_1SimulatedAnnealingOptimizer.html | 186 + ...ctfaiss_1_1SimulatedAnnealingOptimizer.png | Bin 0 -> 900 bytes ...1SimulatedAnnealingParameters-members.html | 113 + ...faiss_1_1SimulatedAnnealingParameters.html | 155 + ...tfaiss_1_1SimulatedAnnealingParameters.png | Bin 0 -> 1259 bytes .../structfaiss_1_1SortedArray-members.html | 112 + docs/html/structfaiss_1_1SortedArray.html | 146 + ...tructfaiss_1_1VectorTransform-members.html | 114 + docs/html/structfaiss_1_1VectorTransform.html | 309 + docs/html/structfaiss_1_1VectorTransform.png | Bin 0 -> 1961 bytes ...tructfaiss_1_1gpu_1_1BitonicMergeStep.html | 110 + ...1Comp_00_01Low_00_01true_01_4-members.html | 105 + ...Dir_00_01Comp_00_01Low_00_01true_01_4.html | 120 + ...1Comp_00_01Low_00_01true_01_4-members.html | 105 + ...Dir_00_01Comp_00_01Low_00_01true_01_4.html | 120 + ...mp_00_01false_00_01false_01_4-members.html | 105 + ..._00_01Comp_00_01false_00_01false_01_4.html | 120 + ...omp_00_01true_00_01false_01_4-members.html | 105 + ...r_00_01Comp_00_01true_00_01false_01_4.html | 120 + ...iss_1_1gpu_1_1BitonicSortStep-members.html | 105 + ...structfaiss_1_1gpu_1_1BitonicSortStep.html | 120 + ...0_011_00_01Dir_00_01Comp_01_4-members.html | 105 + ...00_01V_00_011_00_01Dir_00_01Comp_01_4.html | 120 + .../structfaiss_1_1gpu_1_1BlockMerge.html | 111 + ...Dir_00_01Comp_00_01false_01_4-members.html | 105 + ...1L_00_01Dir_00_01Comp_00_01false_01_4.html | 124 + ...1Dir_00_01Comp_00_01true_01_4-members.html | 105 + ...01L_00_01Dir_00_01Comp_00_01true_01_4.html | 124 + ...ctfaiss_1_1gpu_1_1BlockSelect-members.html | 120 + .../structfaiss_1_1gpu_1_1BlockSelect.html | 239 + ...adQ_00_01ThreadsPerBlock_01_4-members.html | 114 + ...1NumThreadQ_00_01ThreadsPerBlock_01_4.html | 159 + ...uctfaiss_1_1gpu_1_1Comparator-members.html | 106 + .../structfaiss_1_1gpu_1_1Comparator.html | 123 + .../html/structfaiss_1_1gpu_1_1ConvertTo.html | 110 + ..._1_1ConvertTo_3_01float2_01_4-members.html | 105 + ...s_1_1gpu_1_1ConvertTo_3_01float2_01_4.html | 120 + ..._1_1ConvertTo_3_01float4_01_4-members.html | 105 + ...s_1_1gpu_1_1ConvertTo_3_01float4_01_4.html | 120 + ...u_1_1ConvertTo_3_01float_01_4-members.html | 105 + ...ss_1_1gpu_1_1ConvertTo_3_01float_01_4.html | 120 + .../html/structfaiss_1_1gpu_1_1Converter.html | 110 + ...u_1_1Converter_3_01float_01_4-members.html | 105 + ...ss_1_1gpu_1_1Converter_3_01float_01_4.html | 120 + .../structfaiss_1_1gpu_1_1CublasGemm.html | 110 + ..._1_1CublasGemm_3_01float_01_4-members.html | 105 + ...s_1_1gpu_1_1CublasGemm_3_01float_01_4.html | 120 + ...structfaiss_1_1gpu_1_1FinalBlockMerge.html | 110 + ...WarpQ_00_01Dir_00_01Comp_01_4-members.html | 105 + ...00_01NumWarpQ_00_01Dir_00_01Comp_01_4.html | 120 + ...WarpQ_00_01Dir_00_01Comp_01_4-members.html | 105 + ...00_01NumWarpQ_00_01Dir_00_01Comp_01_4.html | 120 + ...WarpQ_00_01Dir_00_01Comp_01_4-members.html | 105 + ...00_01NumWarpQ_00_01Dir_00_01Comp_01_4.html | 120 + ...WarpQ_00_01Dir_00_01Comp_01_4-members.html | 105 + ...00_01NumWarpQ_00_01Dir_00_01Comp_01_4.html | 120 + ...ss_1_1gpu_1_1GpuClonerOptions-members.html | 111 + ...tructfaiss_1_1gpu_1_1GpuClonerOptions.html | 169 + ...structfaiss_1_1gpu_1_1GpuClonerOptions.png | Bin 0 -> 1685 bytes ...u_1_1GpuMultipleClonerOptions-members.html | 113 + ...ss_1_1gpu_1_1GpuMultipleClonerOptions.html | 151 + ...iss_1_1gpu_1_1GpuMultipleClonerOptions.png | Bin 0 -> 1225 bytes ...s_1_1gpu_1_1GpuParameterSpace-members.html | 122 + ...ructfaiss_1_1gpu_1_1GpuParameterSpace.html | 194 + ...tructfaiss_1_1gpu_1_1GpuParameterSpace.png | Bin 0 -> 766 bytes .../structfaiss_1_1gpu_1_1IVFFlatScan.html | 114 + ...tScan_3-1_00_01L2_00_01T_01_4-members.html | 105 + ..._1IVFFlatScan_3-1_00_01L2_00_01T_01_4.html | 120 + ...can_3_010_00_01L2_00_01T_01_4-members.html | 105 + ...IVFFlatScan_3_010_00_01L2_00_01T_01_4.html | 120 + ...01128_00_01L2_00_01float_01_4-members.html | 106 + ...tScan_3_01128_00_01L2_00_01float_01_4.html | 127 + ...01256_00_01L2_00_01float_01_4-members.html | 106 + ...tScan_3_01256_00_01L2_00_01float_01_4.html | 127 + ..._0164_00_01L2_00_01float_01_4-members.html | 106 + ...atScan_3_0164_00_01L2_00_01float_01_4.html | 127 + ...tfaiss_1_1gpu_1_1IndexWrapper-members.html | 111 + .../structfaiss_1_1gpu_1_1IndexWrapper.html | 146 + ...ructfaiss_1_1gpu_1_1IntToLong-members.html | 105 + .../html/structfaiss_1_1gpu_1_1IntToLong.html | 117 + docs/html/structfaiss_1_1gpu_1_1Limits.html | 110 + ...01Pair_3_01K_00_01V_01_4_01_4-members.html | 106 + ...imits_3_01Pair_3_01K_00_01V_01_4_01_4.html | 125 + ...1gpu_1_1Limits_3_01float_01_4-members.html | 106 + ...faiss_1_1gpu_1_1Limits_3_01float_01_4.html | 123 + ...1_1gpu_1_1Limits_3_01int_01_4-members.html | 106 + ...ctfaiss_1_1gpu_1_1Limits_3_01int_01_4.html | 123 + .../structfaiss_1_1gpu_1_1LoadCode32.html | 110 + ...gpu_1_1LoadCode32_3_0112_01_4-members.html | 105 + ...aiss_1_1gpu_1_1LoadCode32_3_0112_01_4.html | 120 + ...gpu_1_1LoadCode32_3_0116_01_4-members.html | 105 + ...aiss_1_1gpu_1_1LoadCode32_3_0116_01_4.html | 120 + ...1gpu_1_1LoadCode32_3_011_01_4-members.html | 105 + ...faiss_1_1gpu_1_1LoadCode32_3_011_01_4.html | 120 + ...gpu_1_1LoadCode32_3_0120_01_4-members.html | 105 + ...aiss_1_1gpu_1_1LoadCode32_3_0120_01_4.html | 120 + ...gpu_1_1LoadCode32_3_0124_01_4-members.html | 105 + ...aiss_1_1gpu_1_1LoadCode32_3_0124_01_4.html | 120 + ...gpu_1_1LoadCode32_3_0128_01_4-members.html | 105 + ...aiss_1_1gpu_1_1LoadCode32_3_0128_01_4.html | 120 + ...1gpu_1_1LoadCode32_3_012_01_4-members.html | 105 + ...faiss_1_1gpu_1_1LoadCode32_3_012_01_4.html | 120 + ...gpu_1_1LoadCode32_3_0132_01_4-members.html | 105 + ...aiss_1_1gpu_1_1LoadCode32_3_0132_01_4.html | 120 + ...1gpu_1_1LoadCode32_3_013_01_4-members.html | 105 + ...faiss_1_1gpu_1_1LoadCode32_3_013_01_4.html | 120 + ...gpu_1_1LoadCode32_3_0140_01_4-members.html | 105 + ...aiss_1_1gpu_1_1LoadCode32_3_0140_01_4.html | 120 + ...gpu_1_1LoadCode32_3_0148_01_4-members.html | 105 + ...aiss_1_1gpu_1_1LoadCode32_3_0148_01_4.html | 120 + ...1gpu_1_1LoadCode32_3_014_01_4-members.html | 105 + ...faiss_1_1gpu_1_1LoadCode32_3_014_01_4.html | 120 + ...gpu_1_1LoadCode32_3_0156_01_4-members.html | 105 + ...aiss_1_1gpu_1_1LoadCode32_3_0156_01_4.html | 120 + ...gpu_1_1LoadCode32_3_0164_01_4-members.html | 105 + ...aiss_1_1gpu_1_1LoadCode32_3_0164_01_4.html | 120 + ...1gpu_1_1LoadCode32_3_018_01_4-members.html | 105 + ...faiss_1_1gpu_1_1LoadCode32_3_018_01_4.html | 120 + ...s_1_1gpu_1_1LoadCodeDistances-members.html | 105 + ...ructfaiss_1_1gpu_1_1LoadCodeDistances.html | 120 + ...ructfaiss_1_1gpu_1_1LoadStore-members.html | 106 + .../html/structfaiss_1_1gpu_1_1LoadStore.html | 123 + .../structfaiss_1_1gpu_1_1Math-members.html | 114 + docs/html/structfaiss_1_1gpu_1_1Math.html | 152 + ..._1gpu_1_1Math_3_01float2_01_4-members.html | 114 + ...tfaiss_1_1gpu_1_1Math_3_01float2_01_4.html | 152 + ..._1gpu_1_1Math_3_01float4_01_4-members.html | 114 + ...tfaiss_1_1gpu_1_1Math_3_01float4_01_4.html | 152 + .../structfaiss_1_1gpu_1_1Max-members.html | 106 + docs/html/structfaiss_1_1gpu_1_1Max.html | 123 + .../structfaiss_1_1gpu_1_1Min-members.html | 106 + docs/html/structfaiss_1_1gpu_1_1Min.html | 123 + .../structfaiss_1_1gpu_1_1Pair-members.html | 112 + docs/html/structfaiss_1_1gpu_1_1Pair.html | 146 + ...iss_1_1gpu_1_1SegmentedReduce-members.html | 108 + ...structfaiss_1_1gpu_1_1SegmentedReduce.html | 134 + ...1_1StackDeviceMemory_1_1Range-members.html | 108 + ..._1_1gpu_1_1StackDeviceMemory_1_1Range.html | 133 + ...1_1StackDeviceMemory_1_1Stack-members.html | 122 + ..._1_1gpu_1_1StackDeviceMemory_1_1Stack.html | 283 + .../structfaiss_1_1gpu_1_1Sum-members.html | 106 + docs/html/structfaiss_1_1gpu_1_1Sum.html | 123 + ...uctfaiss_1_1gpu_1_1TensorInfo-members.html | 109 + .../structfaiss_1_1gpu_1_1TensorInfo.html | 136 + ...ss_1_1gpu_1_1TensorInfoOffset-members.html | 105 + ...tructfaiss_1_1gpu_1_1TensorInfoOffset.html | 120 + ...sorInfoOffset_3_01T_00-1_01_4-members.html | 105 + ...u_1_1TensorInfoOffset_3_01T_00-1_01_4.html | 120 + ...ctfaiss_1_1gpu_1_1ToCPUCloner-members.html | 108 + .../structfaiss_1_1gpu_1_1ToCPUCloner.html | 133 + .../structfaiss_1_1gpu_1_1ToCPUCloner.png | Bin 0 -> 599 bytes ...ctfaiss_1_1gpu_1_1ToGpuCloner-members.html | 118 + .../structfaiss_1_1gpu_1_1ToGpuCloner.html | 169 + .../structfaiss_1_1gpu_1_1ToGpuCloner.png | Bin 0 -> 984 bytes ...1_1gpu_1_1ToGpuClonerMultiple-members.html | 120 + ...ctfaiss_1_1gpu_1_1ToGpuClonerMultiple.html | 175 + ...uctfaiss_1_1gpu_1_1ToGpuClonerMultiple.png | Bin 0 -> 1617 bytes ...uctfaiss_1_1gpu_1_1WarpSelect-members.html | 118 + .../structfaiss_1_1gpu_1_1WarpSelect.html | 234 + ...adQ_00_01ThreadsPerBlock_01_4-members.html | 113 + ...1NumThreadQ_00_01ThreadsPerBlock_01_4.html | 157 + ...1_1traits_1_1DefaultPtrTraits-members.html | 105 + ..._1_1gpu_1_1traits_1_1DefaultPtrTraits.html | 120 + ..._1traits_1_1RestrictPtrTraits-members.html | 105 + ...1_1gpu_1_1traits_1_1RestrictPtrTraits.html | 120 + docs/html/sync_off.png | Bin 0 -> 853 bytes docs/html/sync_on.png | Bin 0 -> 845 bytes docs/html/tab_a.png | Bin 0 -> 142 bytes docs/html/tab_b.png | Bin 0 -> 169 bytes docs/html/tab_h.png | Bin 0 -> 177 bytes docs/html/tab_s.png | Bin 0 -> 184 bytes docs/html/tabs.css | 60 + docs/html/test__blas_8cpp_source.html | 184 + .../test__ivfpq__indexing_8cpp_source.html | 201 + docs/html/utils_8cpp_source.html | 1917 + docs/html/utils_8h_source.html | 476 + example_makefiles/makefile.inc.Linux | 130 + example_makefiles/makefile.inc.Mac.brew | 103 + example_makefiles/makefile.inc.Mac.port | 109 + faiss.h | 46 + faiss.py | 249 + gpu/GpuAutoTune.cpp | 336 + gpu/GpuAutoTune.h | 81 + gpu/GpuIndex.cu | 32 + gpu/GpuIndex.h | 46 + gpu/GpuIndexFlat.cu | 541 + gpu/GpuIndexFlat.h | 182 + gpu/GpuIndexIVF.cu | 290 + gpu/GpuIndexIVF.h | 103 + gpu/GpuIndexIVFFlat.cu | 292 + gpu/GpuIndexIVFFlat.h | 103 + gpu/GpuIndexIVFPQ.cu | 475 + gpu/GpuIndexIVFPQ.h | 145 + gpu/GpuIndicesOptions.h | 33 + gpu/GpuResources.cpp | 45 + gpu/GpuResources.h | 56 + gpu/IndexProxy.cpp | 221 + gpu/IndexProxy.h | 105 + gpu/Makefile | 1162 + gpu/StandardGpuResources.cpp | 206 + gpu/StandardGpuResources.h | 96 + gpu/impl/BroadcastSum.cu | 366 + gpu/impl/BroadcastSum.cuh | 52 + gpu/impl/Distance.cu | 341 + gpu/impl/Distance.cuh | 73 + gpu/impl/FlatIndex.cu | 242 + gpu/impl/FlatIndex.cuh | 113 + gpu/impl/IVFBase.cu | 285 + gpu/impl/IVFBase.cuh | 126 + gpu/impl/IVFFlat.cu | 385 + gpu/impl/IVFFlat.cuh | 67 + gpu/impl/IVFFlatScan.cu | 908 + gpu/impl/IVFFlatScan.cuh | 38 + gpu/impl/IVFPQ.cu | 708 + gpu/impl/IVFPQ.cuh | 141 + gpu/impl/IVFUtils.cu | 79 + gpu/impl/IVFUtils.cuh | 54 + gpu/impl/IVFUtilsSelect1.cu | 147 + gpu/impl/IVFUtilsSelect2.cu | 216 + gpu/impl/InvertedListAppend.cu | 268 + gpu/impl/InvertedListAppend.cuh | 53 + gpu/impl/L2Norm.cu | 257 + gpu/impl/L2Norm.cuh | 31 + gpu/impl/L2Select.cu | 253 + gpu/impl/L2Select.cuh | 35 + gpu/impl/PQCodeDistances.cu | 508 + gpu/impl/PQCodeDistances.cuh | 44 + gpu/impl/PQCodeLoad.cuh | 365 + gpu/impl/PQScanMultiPassNoPrecomputed.cu | 608 + gpu/impl/PQScanMultiPassNoPrecomputed.cuh | 46 + gpu/impl/PQScanMultiPassPrecomputed.cu | 563 + gpu/impl/PQScanMultiPassPrecomputed.cuh | 44 + gpu/impl/RemapIndices.cpp | 46 + gpu/impl/RemapIndices.h | 27 + gpu/impl/VectorResidual.cu | 100 + gpu/impl/VectorResidual.cuh | 34 + gpu/perf/CompareFlat.cu | 138 + gpu/perf/CompareIVFFlat.cu | 149 + gpu/perf/CompareIVFPQ.cu | 158 + gpu/perf/CompareIVFPQGrid.cu | 197 + gpu/perf/IndexWrapper-inl.h | 74 + gpu/perf/IndexWrapper.h | 42 + gpu/perf/PerfClustering.cpp | 111 + gpu/perf/PerfIVFPQAdd.cpp | 142 + gpu/perf/WriteIndex.cpp | 105 + gpu/test/TestGpuIndexFlat.cpp | 222 + gpu/test/TestGpuIndexIVFFlat.cpp | 475 + gpu/test/TestGpuIndexIVFPQ.cpp | 374 + gpu/test/TestGpuSelect.cu | 188 + gpu/test/TestUtils.cpp | 278 + gpu/test/TestUtils.h | 81 + gpu/test/demo_ivfpq_indexing_gpu.cpp | 163 + gpu/test/test_gpu_index.py | 76 + gpu/utils/BlockSelectFloat.cu | 81 + gpu/utils/BlockSelectHalf.cu | 85 + gpu/utils/BlockSelectKernel.cuh | 74 + gpu/utils/Comparators.cuh | 53 + gpu/utils/ConversionOperators.cuh | 72 + gpu/utils/CopyUtils.cuh | 90 + gpu/utils/DeviceDefs.cuh | 62 + gpu/utils/DeviceMemory.cpp | 84 + gpu/utils/DeviceMemory.h | 74 + gpu/utils/DeviceTensor-inl.cuh | 212 + gpu/utils/DeviceTensor.cuh | 105 + gpu/utils/DeviceUtils.cpp | 164 + gpu/utils/DeviceUtils.h | 154 + gpu/utils/DeviceVector.cuh | 176 + gpu/utils/Float16.cu | 116 + gpu/utils/Float16.cuh | 157 + gpu/utils/HostTensor-inl.cuh | 153 + gpu/utils/HostTensor.cuh | 87 + gpu/utils/Limits.cuh | 83 + gpu/utils/LoadStoreOperators.cuh | 69 + gpu/utils/MathOperators.cuh | 568 + gpu/utils/MatrixMult.cu | 271 + gpu/utils/MatrixMult.cuh | 67 + gpu/utils/MergeNetworkBlock.cuh | 204 + gpu/utils/MergeNetworkWarp.cuh | 499 + gpu/utils/NoTypeTensor.cuh | 125 + gpu/utils/Pair.cuh | 72 + gpu/utils/PtxUtils.cuh | 88 + gpu/utils/ReductionOperators.cuh | 76 + gpu/utils/Reductions.cuh | 145 + gpu/utils/Select.cuh | 579 + gpu/utils/StackDeviceMemory.cpp | 231 + gpu/utils/StackDeviceMemory.h | 125 + gpu/utils/StaticUtils.h | 76 + gpu/utils/Tensor-inl.cuh | 624 + gpu/utils/Tensor.cuh | 619 + gpu/utils/ThrustAllocator.cuh | 66 + gpu/utils/Timer.cpp | 63 + gpu/utils/Timer.h | 55 + gpu/utils/Transpose.cuh | 143 + gpu/utils/WarpSelectFloat.cu | 81 + gpu/utils/WarpSelectHalf.cu | 85 + gpu/utils/WarpSelectKernel.cuh | 75 + gpu/utils/WarpShuffles.cuh | 102 + gpu/utils/WorkerThread.cpp | 114 + gpu/utils/WorkerThread.h | 64 + gpu/utils/blockselect/BlockSelectFloat1.cu | 18 + gpu/utils/blockselect/BlockSelectFloat128.cu | 18 + gpu/utils/blockselect/BlockSelectFloat256.cu | 18 + gpu/utils/blockselect/BlockSelectFloat32.cu | 18 + gpu/utils/blockselect/BlockSelectFloat64.cu | 18 + .../blockselect/BlockSelectFloatF1024.cu | 17 + gpu/utils/blockselect/BlockSelectFloatF512.cu | 17 + .../blockselect/BlockSelectFloatT1024.cu | 17 + gpu/utils/blockselect/BlockSelectFloatT512.cu | 17 + gpu/utils/blockselect/BlockSelectHalf1.cu | 20 + gpu/utils/blockselect/BlockSelectHalf128.cu | 20 + gpu/utils/blockselect/BlockSelectHalf256.cu | 20 + gpu/utils/blockselect/BlockSelectHalf32.cu | 20 + gpu/utils/blockselect/BlockSelectHalf64.cu | 20 + gpu/utils/blockselect/BlockSelectHalfF1024.cu | 19 + gpu/utils/blockselect/BlockSelectHalfF512.cu | 19 + gpu/utils/blockselect/BlockSelectHalfT1024.cu | 19 + gpu/utils/blockselect/BlockSelectHalfT512.cu | 19 + gpu/utils/blockselect/BlockSelectImpl.cuh | 48 + gpu/utils/warpselect/WarpSelectFloat1.cu | 18 + gpu/utils/warpselect/WarpSelectFloat128.cu | 18 + gpu/utils/warpselect/WarpSelectFloat256.cu | 18 + gpu/utils/warpselect/WarpSelectFloat32.cu | 18 + gpu/utils/warpselect/WarpSelectFloat64.cu | 18 + gpu/utils/warpselect/WarpSelectFloatF1024.cu | 17 + gpu/utils/warpselect/WarpSelectFloatF512.cu | 17 + gpu/utils/warpselect/WarpSelectFloatT1024.cu | 17 + gpu/utils/warpselect/WarpSelectFloatT512.cu | 17 + gpu/utils/warpselect/WarpSelectHalf1.cu | 20 + gpu/utils/warpselect/WarpSelectHalf128.cu | 20 + gpu/utils/warpselect/WarpSelectHalf256.cu | 20 + gpu/utils/warpselect/WarpSelectHalf32.cu | 20 + gpu/utils/warpselect/WarpSelectHalf64.cu | 20 + gpu/utils/warpselect/WarpSelectHalfF1024.cu | 19 + gpu/utils/warpselect/WarpSelectHalfF512.cu | 19 + gpu/utils/warpselect/WarpSelectHalfT1024.cu | 19 + gpu/utils/warpselect/WarpSelectHalfT512.cu | 19 + gpu/utils/warpselect/WarpSelectImpl.cuh | 49 + hamming.cpp | 686 + hamming.h | 428 + index_io.cpp | 679 + index_io.h | 61 + python/demo_auto_tune.py | 173 + python/demo_ivfpq.py | 62 + python/swigfaiss.py | 2526 + python/swigfaiss_gpu.py | 2887 + python/swigfaiss_gpu_wrap.cxx | 53546 ++++++++++++++++ python/swigfaiss_wrap.cxx | 46892 ++++++++++++++ python/test_build_blocks.py | 60 + python/test_index.py | 53 + swigfaiss.swig | 638 + tests/demo_imi_flat.cpp | 154 + tests/demo_ivfpq_indexing.cpp | 149 + tests/demo_sift1M.cpp | 255 + tests/test_blas.cpp | 86 + tests/test_ivfpq_indexing.cpp | 101 + tutorial/cpp/1-Flat.cpp | 100 + tutorial/cpp/2-IVFFlat.cpp | 83 + tutorial/cpp/3-IVFPQ.cpp | 95 + tutorial/python/1-Flat.py | 32 + tutorial/python/2-IVFFlat.py | 35 + tutorial/python/3-IVFPQ.py | 35 + utils.cpp | 1794 + utils.h | 355 + 1187 files changed, 292950 insertions(+) create mode 100644 .gitignore create mode 100644 AutoTune.cpp create mode 100644 AutoTune.h create mode 100644 AuxIndexStructures.cpp create mode 100644 AuxIndexStructures.h create mode 100644 CONTRIBUTING.md create mode 100644 Clustering.cpp create mode 100644 Clustering.h create mode 100644 FaissAssert.h create mode 100644 Heap.cpp create mode 100644 Heap.h create mode 100644 INSTALL create mode 100644 Index.cpp create mode 100644 Index.h create mode 100644 IndexFlat.cpp create mode 100644 IndexFlat.h create mode 100644 IndexIVF.cpp create mode 100644 IndexIVF.h create mode 100644 IndexIVFPQ.cpp create mode 100644 IndexIVFPQ.h create mode 100644 IndexLSH.cpp create mode 100644 IndexLSH.h create mode 100644 IndexPQ.cpp create mode 100644 IndexPQ.h create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 MetaIndexes.cpp create mode 100644 MetaIndexes.h create mode 100644 PolysemousTraining.cpp create mode 100644 PolysemousTraining.h create mode 100644 ProductQuantizer.cpp create mode 100644 ProductQuantizer.h create mode 100644 README.md create mode 100644 VectorTransform.cpp create mode 100644 VectorTransform.h create mode 100644 benchs/README.md create mode 100644 docs/Doxyfile create mode 100644 docs/html/1-Flat_8cpp_source.html create mode 100644 docs/html/2-IVFFlat_8cpp_source.html create mode 100644 docs/html/3-IVFPQ_8cpp_source.html create mode 100644 docs/html/AutoTune_8cpp_source.html create mode 100644 docs/html/AutoTune_8h_source.html create mode 100644 docs/html/AuxIndexStructures_8cpp_source.html create mode 100644 docs/html/AuxIndexStructures_8h_source.html create mode 100644 docs/html/BlockSelectFloat128_8cu_source.html create mode 100644 docs/html/BlockSelectFloat1_8cu_source.html create mode 100644 docs/html/BlockSelectFloat256_8cu_source.html create mode 100644 docs/html/BlockSelectFloat32_8cu_source.html create mode 100644 docs/html/BlockSelectFloat64_8cu_source.html create mode 100644 docs/html/BlockSelectFloatF1024_8cu_source.html create mode 100644 docs/html/BlockSelectFloatF512_8cu_source.html create mode 100644 docs/html/BlockSelectFloatT1024_8cu_source.html create mode 100644 docs/html/BlockSelectFloatT512_8cu_source.html create mode 100644 docs/html/BlockSelectFloat_8cu_source.html create mode 100644 docs/html/BlockSelectHalf128_8cu_source.html create mode 100644 docs/html/BlockSelectHalf1_8cu_source.html create mode 100644 docs/html/BlockSelectHalf256_8cu_source.html create mode 100644 docs/html/BlockSelectHalf32_8cu_source.html create mode 100644 docs/html/BlockSelectHalf64_8cu_source.html create mode 100644 docs/html/BlockSelectHalfF1024_8cu_source.html create mode 100644 docs/html/BlockSelectHalfF512_8cu_source.html create mode 100644 docs/html/BlockSelectHalfT1024_8cu_source.html create mode 100644 docs/html/BlockSelectHalfT512_8cu_source.html create mode 100644 docs/html/BlockSelectHalf_8cu_source.html create mode 100644 docs/html/BlockSelectImpl_8cuh_source.html create mode 100644 docs/html/BlockSelectKernel_8cuh_source.html create mode 100644 docs/html/BroadcastSum_8cu_source.html create mode 100644 docs/html/BroadcastSum_8cuh_source.html create mode 100644 docs/html/Clustering_8cpp_source.html create mode 100644 docs/html/Clustering_8h_source.html create mode 100644 docs/html/Comparators_8cuh_source.html create mode 100644 docs/html/CompareFlat_8cu_source.html create mode 100644 docs/html/CompareIVFFlat_8cu_source.html create mode 100644 docs/html/CompareIVFPQGrid_8cu_source.html create mode 100644 docs/html/CompareIVFPQ_8cu_source.html create mode 100644 docs/html/ConversionOperators_8cuh_source.html create mode 100644 docs/html/CopyUtils_8cuh_source.html create mode 100644 docs/html/DeviceDefs_8cuh_source.html create mode 100644 docs/html/DeviceMemory_8cpp_source.html create mode 100644 docs/html/DeviceMemory_8h_source.html create mode 100644 docs/html/DeviceTensor-inl_8cuh_source.html create mode 100644 docs/html/DeviceTensor_8cuh_source.html create mode 100644 docs/html/DeviceUtils_8cpp_source.html create mode 100644 docs/html/DeviceUtils_8h_source.html create mode 100644 docs/html/DeviceVector_8cuh_source.html create mode 100644 docs/html/Distance_8cu_source.html create mode 100644 docs/html/Distance_8cuh_source.html create mode 100644 docs/html/FaissAssert_8h_source.html create mode 100644 docs/html/FlatIndex_8cu_source.html create mode 100644 docs/html/FlatIndex_8cuh_source.html create mode 100644 docs/html/Float16_8cu_source.html create mode 100644 docs/html/Float16_8cuh_source.html create mode 100644 docs/html/GpuAutoTune_8cpp_source.html create mode 100644 docs/html/GpuAutoTune_8h_source.html create mode 100644 docs/html/GpuIndexFlat_8cu_source.html create mode 100644 docs/html/GpuIndexFlat_8h_source.html create mode 100644 docs/html/GpuIndexIVFFlat_8cu_source.html create mode 100644 docs/html/GpuIndexIVFFlat_8h_source.html create mode 100644 docs/html/GpuIndexIVFPQ_8cu_source.html create mode 100644 docs/html/GpuIndexIVFPQ_8h_source.html create mode 100644 docs/html/GpuIndexIVF_8cu_source.html create mode 100644 docs/html/GpuIndexIVF_8h_source.html create mode 100644 docs/html/GpuIndex_8cu_source.html create mode 100644 docs/html/GpuIndex_8h_source.html create mode 100644 docs/html/GpuIndicesOptions_8h_source.html create mode 100644 docs/html/GpuResources_8cpp_source.html create mode 100644 docs/html/GpuResources_8h_source.html create mode 100644 docs/html/Heap_8cpp_source.html create mode 100644 docs/html/Heap_8h_source.html create mode 100644 docs/html/HostTensor-inl_8cuh_source.html create mode 100644 docs/html/HostTensor_8cuh_source.html create mode 100644 docs/html/IVFBase_8cu_source.html create mode 100644 docs/html/IVFBase_8cuh_source.html create mode 100644 docs/html/IVFFlatScan_8cu_source.html create mode 100644 docs/html/IVFFlatScan_8cuh_source.html create mode 100644 docs/html/IVFFlat_8cu_source.html create mode 100644 docs/html/IVFFlat_8cuh_source.html create mode 100644 docs/html/IVFPQ_8cu_source.html create mode 100644 docs/html/IVFPQ_8cuh_source.html create mode 100644 docs/html/IVFUtilsSelect1_8cu_source.html create mode 100644 docs/html/IVFUtilsSelect2_8cu_source.html create mode 100644 docs/html/IVFUtils_8cu_source.html create mode 100644 docs/html/IVFUtils_8cuh_source.html create mode 100644 docs/html/IndexFlat_8cpp_source.html create mode 100644 docs/html/IndexFlat_8h_source.html create mode 100644 docs/html/IndexIVFPQ_8cpp_source.html create mode 100644 docs/html/IndexIVFPQ_8h_source.html create mode 100644 docs/html/IndexIVF_8cpp_source.html create mode 100644 docs/html/IndexIVF_8h_source.html create mode 100644 docs/html/IndexLSH_8cpp_source.html create mode 100644 docs/html/IndexLSH_8h_source.html create mode 100644 docs/html/IndexPQ_8cpp_source.html create mode 100644 docs/html/IndexPQ_8h_source.html create mode 100644 docs/html/IndexProxy_8cpp_source.html create mode 100644 docs/html/IndexProxy_8h_source.html create mode 100644 docs/html/IndexWrapper-inl_8h_source.html create mode 100644 docs/html/IndexWrapper_8h_source.html create mode 100644 docs/html/Index_8cpp_source.html create mode 100644 docs/html/Index_8h_source.html create mode 100644 docs/html/InvertedListAppend_8cu_source.html create mode 100644 docs/html/InvertedListAppend_8cuh_source.html create mode 100644 docs/html/L2Norm_8cu_source.html create mode 100644 docs/html/L2Norm_8cuh_source.html create mode 100644 docs/html/L2Select_8cu_source.html create mode 100644 docs/html/L2Select_8cuh_source.html create mode 100644 docs/html/Limits_8cuh_source.html create mode 100644 docs/html/LoadStoreOperators_8cuh_source.html create mode 100644 docs/html/MathOperators_8cuh_source.html create mode 100644 docs/html/MatrixMult_8cu_source.html create mode 100644 docs/html/MatrixMult_8cuh_source.html create mode 100644 docs/html/MergeNetworkBlock_8cuh_source.html create mode 100644 docs/html/MergeNetworkWarp_8cuh_source.html create mode 100644 docs/html/MetaIndexes_8cpp_source.html create mode 100644 docs/html/MetaIndexes_8h_source.html create mode 100644 docs/html/NoTypeTensor_8cuh_source.html create mode 100644 docs/html/PQCodeDistances_8cu_source.html create mode 100644 docs/html/PQCodeDistances_8cuh_source.html create mode 100644 docs/html/PQCodeLoad_8cuh_source.html create mode 100644 docs/html/PQScanMultiPassNoPrecomputed_8cu_source.html create mode 100644 docs/html/PQScanMultiPassNoPrecomputed_8cuh_source.html create mode 100644 docs/html/PQScanMultiPassPrecomputed_8cu_source.html create mode 100644 docs/html/PQScanMultiPassPrecomputed_8cuh_source.html create mode 100644 docs/html/Pair_8cuh_source.html create mode 100644 docs/html/PerfClustering_8cpp_source.html create mode 100644 docs/html/PerfIVFPQAdd_8cpp_source.html create mode 100644 docs/html/PolysemousTraining_8cpp_source.html create mode 100644 docs/html/PolysemousTraining_8h_source.html create mode 100644 docs/html/ProductQuantizer_8cpp_source.html create mode 100644 docs/html/ProductQuantizer_8h_source.html create mode 100644 docs/html/PtxUtils_8cuh_source.html create mode 100644 docs/html/ReductionOperators_8cuh_source.html create mode 100644 docs/html/Reductions_8cuh_source.html create mode 100644 docs/html/RemapIndices_8cpp_source.html create mode 100644 docs/html/RemapIndices_8h_source.html create mode 100644 docs/html/Select_8cuh_source.html create mode 100644 docs/html/StackDeviceMemory_8cpp_source.html create mode 100644 docs/html/StackDeviceMemory_8h_source.html create mode 100644 docs/html/StandardGpuResources_8cpp_source.html create mode 100644 docs/html/StandardGpuResources_8h_source.html create mode 100644 docs/html/StaticUtils_8h_source.html create mode 100644 docs/html/Tensor-inl_8cuh_source.html create mode 100644 docs/html/Tensor_8cuh_source.html create mode 100644 docs/html/TestGpuIndexFlat_8cpp_source.html create mode 100644 docs/html/TestGpuIndexIVFFlat_8cpp_source.html create mode 100644 docs/html/TestGpuIndexIVFPQ_8cpp_source.html create mode 100644 docs/html/TestGpuSelect_8cu_source.html create mode 100644 docs/html/TestUtils_8cpp_source.html create mode 100644 docs/html/TestUtils_8h_source.html create mode 100644 docs/html/ThrustAllocator_8cuh_source.html create mode 100644 docs/html/Timer_8cpp_source.html create mode 100644 docs/html/Timer_8h_source.html create mode 100644 docs/html/Transpose_8cuh_source.html create mode 100644 docs/html/VectorResidual_8cu_source.html create mode 100644 docs/html/VectorResidual_8cuh_source.html create mode 100644 docs/html/VectorTransform_8cpp_source.html create mode 100644 docs/html/VectorTransform_8h_source.html create mode 100644 docs/html/WarpSelectFloat128_8cu_source.html create mode 100644 docs/html/WarpSelectFloat1_8cu_source.html create mode 100644 docs/html/WarpSelectFloat256_8cu_source.html create mode 100644 docs/html/WarpSelectFloat32_8cu_source.html create mode 100644 docs/html/WarpSelectFloat64_8cu_source.html create mode 100644 docs/html/WarpSelectFloatF1024_8cu_source.html create mode 100644 docs/html/WarpSelectFloatF512_8cu_source.html create mode 100644 docs/html/WarpSelectFloatT1024_8cu_source.html create mode 100644 docs/html/WarpSelectFloatT512_8cu_source.html create mode 100644 docs/html/WarpSelectFloat_8cu_source.html create mode 100644 docs/html/WarpSelectHalf128_8cu_source.html create mode 100644 docs/html/WarpSelectHalf1_8cu_source.html create mode 100644 docs/html/WarpSelectHalf256_8cu_source.html create mode 100644 docs/html/WarpSelectHalf32_8cu_source.html create mode 100644 docs/html/WarpSelectHalf64_8cu_source.html create mode 100644 docs/html/WarpSelectHalfF1024_8cu_source.html create mode 100644 docs/html/WarpSelectHalfF512_8cu_source.html create mode 100644 docs/html/WarpSelectHalfT1024_8cu_source.html create mode 100644 docs/html/WarpSelectHalfT512_8cu_source.html create mode 100644 docs/html/WarpSelectHalf_8cu_source.html create mode 100644 docs/html/WarpSelectImpl_8cuh_source.html create mode 100644 docs/html/WarpSelectKernel_8cuh_source.html create mode 100644 docs/html/WarpShuffles_8cuh_source.html create mode 100644 docs/html/WorkerThread_8cpp_source.html create mode 100644 docs/html/WorkerThread_8h_source.html create mode 100644 docs/html/WriteIndex_8cpp_source.html create mode 100644 docs/html/annotated.html create mode 100644 docs/html/bc_s.png create mode 100644 docs/html/bdwn.png create mode 100644 docs/html/classes.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1CpuTimer-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1CpuTimer.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1CublasHandleScope-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1CublasHandleScope.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1CudaEvent-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1CudaEvent.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1DeviceMemory-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1DeviceMemory.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1DeviceMemory.png create mode 100644 docs/html/classfaiss_1_1gpu_1_1DeviceMemoryReservation-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1DeviceMemoryReservation.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1DeviceScope-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1DeviceScope.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1DeviceTensor-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1DeviceTensor.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1DeviceTensor.png create mode 100644 docs/html/classfaiss_1_1gpu_1_1DeviceVector-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1DeviceVector.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1FlatIndex-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1FlatIndex.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndex-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndex.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndex.png create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndexFlat-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndexFlat.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndexFlat.png create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndexFlatIP-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndexFlatIP.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndexFlatIP.png create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndexFlatL2-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndexFlatL2.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndexFlatL2.png create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndexIVF-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndexIVF.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndexIVF.png create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndexIVFFlat-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndexIVFFlat.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndexIVFFlat.png create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndexIVFPQ-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndexIVFPQ.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuIndexIVFPQ.png create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuResources-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuResources.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuResources.png create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuResourcesThrustAllocator-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1GpuResourcesThrustAllocator.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1HostTensor-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1HostTensor.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1HostTensor.png create mode 100644 docs/html/classfaiss_1_1gpu_1_1IVFBase-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1IVFBase.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1IVFBase.png create mode 100644 docs/html/classfaiss_1_1gpu_1_1IVFFlat-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1IVFFlat.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1IVFFlat.png create mode 100644 docs/html/classfaiss_1_1gpu_1_1IVFPQ-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1IVFPQ.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1IVFPQ.png create mode 100644 docs/html/classfaiss_1_1gpu_1_1IndexProxy-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1IndexProxy.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1IndexProxy.png create mode 100644 docs/html/classfaiss_1_1gpu_1_1KernelTimer-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1KernelTimer.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1NoTypeTensor-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1NoTypeTensor.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1StackDeviceMemory-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1StackDeviceMemory.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1StackDeviceMemory.png create mode 100644 docs/html/classfaiss_1_1gpu_1_1StandardGpuResources-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1StandardGpuResources.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1StandardGpuResources.png create mode 100644 docs/html/classfaiss_1_1gpu_1_1Tensor-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1Tensor.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1Tensor.png create mode 100644 docs/html/classfaiss_1_1gpu_1_1WorkerThread-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1WorkerThread.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1detail_1_1SubTensor-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1detail_1_1SubTensor.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1detail_1_1SubTensor_3_01TensorType_00_010_00_01PtrTraits_01_4-members.html create mode 100644 docs/html/classfaiss_1_1gpu_1_1detail_1_1SubTensor_3_01TensorType_00_010_00_01PtrTraits_01_4.html create mode 100644 docs/html/closed.png create mode 100644 docs/html/demo__imi__flat_8cpp_source.html create mode 100644 docs/html/demo__ivfpq__indexing_8cpp_source.html create mode 100644 docs/html/demo__ivfpq__indexing__gpu_8cpp_source.html create mode 100644 docs/html/demo__sift1M_8cpp_source.html create mode 100644 docs/html/dir_02dc1cf4df13911cc8cfd20f07047b71.html create mode 100644 docs/html/dir_07c2df013bb20677b8e65a9f18968d2c.html create mode 100644 docs/html/dir_29631f2f8051359899a6ac5f9629d2be.html create mode 100644 docs/html/dir_43be364029218a94c5bcb5d8c78d7156.html create mode 100644 docs/html/dir_498271007b03b2a0521055e88776887b.html create mode 100644 docs/html/dir_49d1182a3b8dfb62757c53ae905481ad.html create mode 100644 docs/html/dir_59425e443f801f1f2fd8bbe4959a3ccf.html create mode 100644 docs/html/dir_6b3ae6988449b0834e9596fad5d75199.html create mode 100644 docs/html/dir_d92646511ea2c8ec2c89d5c37e8b5981.html create mode 100644 docs/html/dir_dbd6ec420472444c618282e2c8178be7.html create mode 100644 docs/html/doxygen.css create mode 100644 docs/html/doxygen.png create mode 100644 docs/html/dynsections.js create mode 100644 docs/html/faiss_8h_source.html create mode 100644 docs/html/files.html create mode 100644 docs/html/ftv2blank.png create mode 100644 docs/html/ftv2cl.png create mode 100644 docs/html/ftv2doc.png create mode 100644 docs/html/ftv2folderclosed.png create mode 100644 docs/html/ftv2folderopen.png create mode 100644 docs/html/ftv2lastnode.png create mode 100644 docs/html/ftv2link.png create mode 100644 docs/html/ftv2mlastnode.png create mode 100644 docs/html/ftv2mnode.png create mode 100644 docs/html/ftv2mo.png create mode 100644 docs/html/ftv2node.png create mode 100644 docs/html/ftv2ns.png create mode 100644 docs/html/ftv2plastnode.png create mode 100644 docs/html/ftv2pnode.png create mode 100644 docs/html/ftv2splitbar.png create mode 100644 docs/html/ftv2vertline.png create mode 100644 docs/html/functions.html create mode 100644 docs/html/functions_0x62.html create mode 100644 docs/html/functions_0x63.html create mode 100644 docs/html/functions_0x64.html create mode 100644 docs/html/functions_0x65.html create mode 100644 docs/html/functions_0x66.html create mode 100644 docs/html/functions_0x67.html create mode 100644 docs/html/functions_0x68.html create mode 100644 docs/html/functions_0x69.html create mode 100644 docs/html/functions_0x6b.html create mode 100644 docs/html/functions_0x6c.html create mode 100644 docs/html/functions_0x6d.html create mode 100644 docs/html/functions_0x6e.html create mode 100644 docs/html/functions_0x6f.html create mode 100644 docs/html/functions_0x70.html create mode 100644 docs/html/functions_0x71.html create mode 100644 docs/html/functions_0x72.html create mode 100644 docs/html/functions_0x73.html create mode 100644 docs/html/functions_0x74.html create mode 100644 docs/html/functions_0x75.html create mode 100644 docs/html/functions_0x76.html create mode 100644 docs/html/functions_0x77.html create mode 100644 docs/html/functions_0x78.html create mode 100644 docs/html/functions_0x7a.html create mode 100644 docs/html/functions_0x7e.html create mode 100644 docs/html/functions_enum.html create mode 100644 docs/html/functions_eval.html create mode 100644 docs/html/functions_func.html create mode 100644 docs/html/functions_func_0x63.html create mode 100644 docs/html/functions_func_0x64.html create mode 100644 docs/html/functions_func_0x65.html create mode 100644 docs/html/functions_func_0x66.html create mode 100644 docs/html/functions_func_0x67.html create mode 100644 docs/html/functions_func_0x68.html create mode 100644 docs/html/functions_func_0x69.html create mode 100644 docs/html/functions_func_0x6b.html create mode 100644 docs/html/functions_func_0x6c.html create mode 100644 docs/html/functions_func_0x6d.html create mode 100644 docs/html/functions_func_0x6e.html create mode 100644 docs/html/functions_func_0x6f.html create mode 100644 docs/html/functions_func_0x70.html create mode 100644 docs/html/functions_func_0x71.html create mode 100644 docs/html/functions_func_0x72.html create mode 100644 docs/html/functions_func_0x73.html create mode 100644 docs/html/functions_func_0x74.html create mode 100644 docs/html/functions_func_0x75.html create mode 100644 docs/html/functions_func_0x76.html create mode 100644 docs/html/functions_func_0x77.html create mode 100644 docs/html/functions_func_0x7a.html create mode 100644 docs/html/functions_func_0x7e.html create mode 100644 docs/html/functions_rela.html create mode 100644 docs/html/functions_type.html create mode 100644 docs/html/functions_vars.html create mode 100644 docs/html/hamming_8cpp_source.html create mode 100644 docs/html/hamming_8h_source.html create mode 100644 docs/html/hierarchy.html create mode 100644 docs/html/index.html create mode 100644 docs/html/index__io_8cpp_source.html create mode 100644 docs/html/index__io_8h_source.html create mode 100644 docs/html/jquery.js create mode 100644 docs/html/namespacefaiss.html create mode 100644 docs/html/namespacefaiss_1_1gpu_1_1detail.html create mode 100644 docs/html/namespacemembers.html create mode 100644 docs/html/namespacemembers_enum.html create mode 100644 docs/html/namespacemembers_func.html create mode 100644 docs/html/namespaces.html create mode 100644 docs/html/nav_f.png create mode 100644 docs/html/nav_g.png create mode 100644 docs/html/nav_h.png create mode 100644 docs/html/open.png create mode 100644 docs/html/search/all_61.html create mode 100644 docs/html/search/all_61.js create mode 100644 docs/html/search/all_62.html create mode 100644 docs/html/search/all_62.js create mode 100644 docs/html/search/all_63.html create mode 100644 docs/html/search/all_63.js create mode 100644 docs/html/search/all_64.html create mode 100644 docs/html/search/all_64.js create mode 100644 docs/html/search/all_65.html create mode 100644 docs/html/search/all_65.js create mode 100644 docs/html/search/all_66.html create mode 100644 docs/html/search/all_66.js create mode 100644 docs/html/search/all_67.html create mode 100644 docs/html/search/all_67.js create mode 100644 docs/html/search/all_68.html create mode 100644 docs/html/search/all_68.js create mode 100644 docs/html/search/all_69.html create mode 100644 docs/html/search/all_69.js create mode 100644 docs/html/search/all_6b.html create mode 100644 docs/html/search/all_6b.js create mode 100644 docs/html/search/all_6c.html create mode 100644 docs/html/search/all_6c.js create mode 100644 docs/html/search/all_6d.html create mode 100644 docs/html/search/all_6d.js create mode 100644 docs/html/search/all_6e.html create mode 100644 docs/html/search/all_6e.js create mode 100644 docs/html/search/all_6f.html create mode 100644 docs/html/search/all_6f.js create mode 100644 docs/html/search/all_70.html create mode 100644 docs/html/search/all_70.js create mode 100644 docs/html/search/all_71.html create mode 100644 docs/html/search/all_71.js create mode 100644 docs/html/search/all_72.html create mode 100644 docs/html/search/all_72.js create mode 100644 docs/html/search/all_73.html create mode 100644 docs/html/search/all_73.js create mode 100644 docs/html/search/all_74.html create mode 100644 docs/html/search/all_74.js create mode 100644 docs/html/search/all_75.html create mode 100644 docs/html/search/all_75.js create mode 100644 docs/html/search/all_76.html create mode 100644 docs/html/search/all_76.js create mode 100644 docs/html/search/all_77.html create mode 100644 docs/html/search/all_77.js create mode 100644 docs/html/search/all_78.html create mode 100644 docs/html/search/all_78.js create mode 100644 docs/html/search/all_7a.html create mode 100644 docs/html/search/all_7a.js create mode 100644 docs/html/search/all_7e.html create mode 100644 docs/html/search/all_7e.js create mode 100644 docs/html/search/classes_61.html create mode 100644 docs/html/search/classes_61.js create mode 100644 docs/html/search/classes_62.html create mode 100644 docs/html/search/classes_62.js create mode 100644 docs/html/search/classes_63.html create mode 100644 docs/html/search/classes_63.js create mode 100644 docs/html/search/classes_64.html create mode 100644 docs/html/search/classes_64.js create mode 100644 docs/html/search/classes_66.html create mode 100644 docs/html/search/classes_66.js create mode 100644 docs/html/search/classes_67.html create mode 100644 docs/html/search/classes_67.js create mode 100644 docs/html/search/classes_68.html create mode 100644 docs/html/search/classes_68.js create mode 100644 docs/html/search/classes_69.html create mode 100644 docs/html/search/classes_69.js create mode 100644 docs/html/search/classes_6b.html create mode 100644 docs/html/search/classes_6b.js create mode 100644 docs/html/search/classes_6c.html create mode 100644 docs/html/search/classes_6c.js create mode 100644 docs/html/search/classes_6d.html create mode 100644 docs/html/search/classes_6d.js create mode 100644 docs/html/search/classes_6e.html create mode 100644 docs/html/search/classes_6e.js create mode 100644 docs/html/search/classes_6f.html create mode 100644 docs/html/search/classes_6f.js create mode 100644 docs/html/search/classes_70.html create mode 100644 docs/html/search/classes_70.js create mode 100644 docs/html/search/classes_71.html create mode 100644 docs/html/search/classes_71.js create mode 100644 docs/html/search/classes_72.html create mode 100644 docs/html/search/classes_72.js create mode 100644 docs/html/search/classes_73.html create mode 100644 docs/html/search/classes_73.js create mode 100644 docs/html/search/classes_74.html create mode 100644 docs/html/search/classes_74.js create mode 100644 docs/html/search/classes_76.html create mode 100644 docs/html/search/classes_76.js create mode 100644 docs/html/search/classes_77.html create mode 100644 docs/html/search/classes_77.js create mode 100644 docs/html/search/close.png create mode 100644 docs/html/search/enums_61.html create mode 100644 docs/html/search/enums_61.js create mode 100644 docs/html/search/enums_6d.html create mode 100644 docs/html/search/enums_6d.js create mode 100644 docs/html/search/enums_6f.html create mode 100644 docs/html/search/enums_6f.js create mode 100644 docs/html/search/enums_73.html create mode 100644 docs/html/search/enums_73.js create mode 100644 docs/html/search/enums_74.html create mode 100644 docs/html/search/enums_74.js create mode 100644 docs/html/search/enumvalues_61.html create mode 100644 docs/html/search/enumvalues_61.js create mode 100644 docs/html/search/enumvalues_6f.html create mode 100644 docs/html/search/enumvalues_6f.js create mode 100644 docs/html/search/enumvalues_73.html create mode 100644 docs/html/search/enumvalues_73.js create mode 100644 docs/html/search/enumvalues_74.html create mode 100644 docs/html/search/enumvalues_74.js create mode 100644 docs/html/search/functions_61.html create mode 100644 docs/html/search/functions_61.js create mode 100644 docs/html/search/functions_62.html create mode 100644 docs/html/search/functions_62.js create mode 100644 docs/html/search/functions_63.html create mode 100644 docs/html/search/functions_63.js create mode 100644 docs/html/search/functions_64.html create mode 100644 docs/html/search/functions_64.js create mode 100644 docs/html/search/functions_65.html create mode 100644 docs/html/search/functions_65.js create mode 100644 docs/html/search/functions_66.html create mode 100644 docs/html/search/functions_66.js create mode 100644 docs/html/search/functions_67.html create mode 100644 docs/html/search/functions_67.js create mode 100644 docs/html/search/functions_68.html create mode 100644 docs/html/search/functions_68.js create mode 100644 docs/html/search/functions_69.html create mode 100644 docs/html/search/functions_69.js create mode 100644 docs/html/search/functions_6b.html create mode 100644 docs/html/search/functions_6b.js create mode 100644 docs/html/search/functions_6c.html create mode 100644 docs/html/search/functions_6c.js create mode 100644 docs/html/search/functions_6d.html create mode 100644 docs/html/search/functions_6d.js create mode 100644 docs/html/search/functions_6e.html create mode 100644 docs/html/search/functions_6e.js create mode 100644 docs/html/search/functions_6f.html create mode 100644 docs/html/search/functions_6f.js create mode 100644 docs/html/search/functions_70.html create mode 100644 docs/html/search/functions_70.js create mode 100644 docs/html/search/functions_71.html create mode 100644 docs/html/search/functions_71.js create mode 100644 docs/html/search/functions_72.html create mode 100644 docs/html/search/functions_72.js create mode 100644 docs/html/search/functions_73.html create mode 100644 docs/html/search/functions_73.js create mode 100644 docs/html/search/functions_74.html create mode 100644 docs/html/search/functions_74.js create mode 100644 docs/html/search/functions_75.html create mode 100644 docs/html/search/functions_75.js create mode 100644 docs/html/search/functions_76.html create mode 100644 docs/html/search/functions_76.js create mode 100644 docs/html/search/functions_77.html create mode 100644 docs/html/search/functions_77.js create mode 100644 docs/html/search/functions_7a.html create mode 100644 docs/html/search/functions_7a.js create mode 100644 docs/html/search/functions_7e.html create mode 100644 docs/html/search/functions_7e.js create mode 100644 docs/html/search/mag_sel.png create mode 100644 docs/html/search/namespaces_66.html create mode 100644 docs/html/search/namespaces_66.js create mode 100644 docs/html/search/nomatches.html create mode 100644 docs/html/search/related_73.html create mode 100644 docs/html/search/related_73.js create mode 100644 docs/html/search/related_74.html create mode 100644 docs/html/search/related_74.js create mode 100644 docs/html/search/search.css create mode 100644 docs/html/search/search.js create mode 100644 docs/html/search/search_l.png create mode 100644 docs/html/search/search_m.png create mode 100644 docs/html/search/search_r.png create mode 100644 docs/html/search/typedefs_68.html create mode 100644 docs/html/search/typedefs_68.js create mode 100644 docs/html/search/typedefs_69.html create mode 100644 docs/html/search/typedefs_69.js create mode 100644 docs/html/search/variables_61.html create mode 100644 docs/html/search/variables_61.js create mode 100644 docs/html/search/variables_62.html create mode 100644 docs/html/search/variables_62.js create mode 100644 docs/html/search/variables_63.html create mode 100644 docs/html/search/variables_63.js create mode 100644 docs/html/search/variables_64.html create mode 100644 docs/html/search/variables_64.js create mode 100644 docs/html/search/variables_65.html create mode 100644 docs/html/search/variables_65.js create mode 100644 docs/html/search/variables_67.html create mode 100644 docs/html/search/variables_67.js create mode 100644 docs/html/search/variables_68.html create mode 100644 docs/html/search/variables_68.js create mode 100644 docs/html/search/variables_69.html create mode 100644 docs/html/search/variables_69.js create mode 100644 docs/html/search/variables_6b.html create mode 100644 docs/html/search/variables_6b.js create mode 100644 docs/html/search/variables_6c.html create mode 100644 docs/html/search/variables_6c.js create mode 100644 docs/html/search/variables_6d.html create mode 100644 docs/html/search/variables_6d.js create mode 100644 docs/html/search/variables_6e.html create mode 100644 docs/html/search/variables_6e.js create mode 100644 docs/html/search/variables_6f.html create mode 100644 docs/html/search/variables_6f.js create mode 100644 docs/html/search/variables_70.html create mode 100644 docs/html/search/variables_70.js create mode 100644 docs/html/search/variables_71.html create mode 100644 docs/html/search/variables_71.js create mode 100644 docs/html/search/variables_72.html create mode 100644 docs/html/search/variables_72.js create mode 100644 docs/html/search/variables_73.html create mode 100644 docs/html/search/variables_73.js create mode 100644 docs/html/search/variables_74.html create mode 100644 docs/html/search/variables_74.js create mode 100644 docs/html/search/variables_75.html create mode 100644 docs/html/search/variables_75.js create mode 100644 docs/html/search/variables_76.html create mode 100644 docs/html/search/variables_76.js create mode 100644 docs/html/search/variables_77.html create mode 100644 docs/html/search/variables_77.js create mode 100644 docs/html/search/variables_78.html create mode 100644 docs/html/search/variables_78.js create mode 100644 docs/html/structOptions-members.html create mode 100644 docs/html/structOptions.html create mode 100644 docs/html/structfaiss_1_1ArgSort-members.html create mode 100644 docs/html/structfaiss_1_1ArgSort.html create mode 100644 docs/html/structfaiss_1_1AutoTuneCriterion-members.html create mode 100644 docs/html/structfaiss_1_1AutoTuneCriterion.html create mode 100644 docs/html/structfaiss_1_1AutoTuneCriterion.png create mode 100644 docs/html/structfaiss_1_1BaseShiftDistanceCorrection-members.html create mode 100644 docs/html/structfaiss_1_1BaseShiftDistanceCorrection.html create mode 100644 docs/html/structfaiss_1_1BufferList-members.html create mode 100644 docs/html/structfaiss_1_1BufferList.html create mode 100644 docs/html/structfaiss_1_1BufferList.png create mode 100644 docs/html/structfaiss_1_1BufferList_1_1Buffer-members.html create mode 100644 docs/html/structfaiss_1_1BufferList_1_1Buffer.html create mode 100644 docs/html/structfaiss_1_1CMax-members.html create mode 100644 docs/html/structfaiss_1_1CMax.html create mode 100644 docs/html/structfaiss_1_1CMin-members.html create mode 100644 docs/html/structfaiss_1_1CMin.html create mode 100644 docs/html/structfaiss_1_1Cloner-members.html create mode 100644 docs/html/structfaiss_1_1Cloner.html create mode 100644 docs/html/structfaiss_1_1Cloner.png create mode 100644 docs/html/structfaiss_1_1Clustering-members.html create mode 100644 docs/html/structfaiss_1_1Clustering.html create mode 100644 docs/html/structfaiss_1_1Clustering.png create mode 100644 docs/html/structfaiss_1_1ClusteringParameters-members.html create mode 100644 docs/html/structfaiss_1_1ClusteringParameters.html create mode 100644 docs/html/structfaiss_1_1ClusteringParameters.png create mode 100644 docs/html/structfaiss_1_1CodeCmp-members.html create mode 100644 docs/html/structfaiss_1_1CodeCmp.html create mode 100644 docs/html/structfaiss_1_1GenHammingComputer16-members.html create mode 100644 docs/html/structfaiss_1_1GenHammingComputer16.html create mode 100644 docs/html/structfaiss_1_1GenHammingComputer32-members.html create mode 100644 docs/html/structfaiss_1_1GenHammingComputer32.html create mode 100644 docs/html/structfaiss_1_1GenHammingComputer8-members.html create mode 100644 docs/html/structfaiss_1_1GenHammingComputer8.html create mode 100644 docs/html/structfaiss_1_1GenHammingComputerM8-members.html create mode 100644 docs/html/structfaiss_1_1GenHammingComputerM8.html create mode 100644 docs/html/structfaiss_1_1HammingComputer-members.html create mode 100644 docs/html/structfaiss_1_1HammingComputer.html create mode 100644 docs/html/structfaiss_1_1HammingComputer.png create mode 100644 docs/html/structfaiss_1_1HammingComputer16-members.html create mode 100644 docs/html/structfaiss_1_1HammingComputer16.html create mode 100644 docs/html/structfaiss_1_1HammingComputer20-members.html create mode 100644 docs/html/structfaiss_1_1HammingComputer20.html create mode 100644 docs/html/structfaiss_1_1HammingComputer32-members.html create mode 100644 docs/html/structfaiss_1_1HammingComputer32.html create mode 100644 docs/html/structfaiss_1_1HammingComputer4-members.html create mode 100644 docs/html/structfaiss_1_1HammingComputer4.html create mode 100644 docs/html/structfaiss_1_1HammingComputer64-members.html create mode 100644 docs/html/structfaiss_1_1HammingComputer64.html create mode 100644 docs/html/structfaiss_1_1HammingComputer8-members.html create mode 100644 docs/html/structfaiss_1_1HammingComputer8.html create mode 100644 docs/html/structfaiss_1_1HammingComputerM4-members.html create mode 100644 docs/html/structfaiss_1_1HammingComputerM4.html create mode 100644 docs/html/structfaiss_1_1HammingComputerM8-members.html create mode 100644 docs/html/structfaiss_1_1HammingComputerM8.html create mode 100644 docs/html/structfaiss_1_1HammingComputerM8.png create mode 100644 docs/html/structfaiss_1_1HeapArray-members.html create mode 100644 docs/html/structfaiss_1_1HeapArray.html create mode 100644 docs/html/structfaiss_1_1IDSelector-members.html create mode 100644 docs/html/structfaiss_1_1IDSelector.html create mode 100644 docs/html/structfaiss_1_1IDSelector.png create mode 100644 docs/html/structfaiss_1_1IDSelectorBatch-members.html create mode 100644 docs/html/structfaiss_1_1IDSelectorBatch.html create mode 100644 docs/html/structfaiss_1_1IDSelectorBatch.png create mode 100644 docs/html/structfaiss_1_1IDSelectorRange-members.html create mode 100644 docs/html/structfaiss_1_1IDSelectorRange.html create mode 100644 docs/html/structfaiss_1_1IDSelectorRange.png create mode 100644 docs/html/structfaiss_1_1Index-members.html create mode 100644 docs/html/structfaiss_1_1Index.html create mode 100644 docs/html/structfaiss_1_1Index.png create mode 100644 docs/html/structfaiss_1_1IndexFlat-members.html create mode 100644 docs/html/structfaiss_1_1IndexFlat.html create mode 100644 docs/html/structfaiss_1_1IndexFlat.png create mode 100644 docs/html/structfaiss_1_1IndexFlat1D-members.html create mode 100644 docs/html/structfaiss_1_1IndexFlat1D.html create mode 100644 docs/html/structfaiss_1_1IndexFlat1D.png create mode 100644 docs/html/structfaiss_1_1IndexFlatIP-members.html create mode 100644 docs/html/structfaiss_1_1IndexFlatIP.html create mode 100644 docs/html/structfaiss_1_1IndexFlatIP.png create mode 100644 docs/html/structfaiss_1_1IndexFlatL2-members.html create mode 100644 docs/html/structfaiss_1_1IndexFlatL2.html create mode 100644 docs/html/structfaiss_1_1IndexFlatL2.png create mode 100644 docs/html/structfaiss_1_1IndexFlatL2BaseShift-members.html create mode 100644 docs/html/structfaiss_1_1IndexFlatL2BaseShift.html create mode 100644 docs/html/structfaiss_1_1IndexFlatL2BaseShift.png create mode 100644 docs/html/structfaiss_1_1IndexIDMap-members.html create mode 100644 docs/html/structfaiss_1_1IndexIDMap.html create mode 100644 docs/html/structfaiss_1_1IndexIDMap.png create mode 100644 docs/html/structfaiss_1_1IndexIVF-members.html create mode 100644 docs/html/structfaiss_1_1IndexIVF.html create mode 100644 docs/html/structfaiss_1_1IndexIVF.png create mode 100644 docs/html/structfaiss_1_1IndexIVFFlat-members.html create mode 100644 docs/html/structfaiss_1_1IndexIVFFlat.html create mode 100644 docs/html/structfaiss_1_1IndexIVFFlat.png create mode 100644 docs/html/structfaiss_1_1IndexIVFPQ-members.html create mode 100644 docs/html/structfaiss_1_1IndexIVFPQ.html create mode 100644 docs/html/structfaiss_1_1IndexIVFPQ.png create mode 100644 docs/html/structfaiss_1_1IndexIVFPQCompact-members.html create mode 100644 docs/html/structfaiss_1_1IndexIVFPQCompact.html create mode 100644 docs/html/structfaiss_1_1IndexIVFPQCompact.png create mode 100644 docs/html/structfaiss_1_1IndexIVFPQR-members.html create mode 100644 docs/html/structfaiss_1_1IndexIVFPQR.html create mode 100644 docs/html/structfaiss_1_1IndexIVFPQR.png create mode 100644 docs/html/structfaiss_1_1IndexIVFPQStats-members.html create mode 100644 docs/html/structfaiss_1_1IndexIVFPQStats.html create mode 100644 docs/html/structfaiss_1_1IndexLSH-members.html create mode 100644 docs/html/structfaiss_1_1IndexLSH.html create mode 100644 docs/html/structfaiss_1_1IndexLSH.png create mode 100644 docs/html/structfaiss_1_1IndexPQ-members.html create mode 100644 docs/html/structfaiss_1_1IndexPQ.html create mode 100644 docs/html/structfaiss_1_1IndexPQ.png create mode 100644 docs/html/structfaiss_1_1IndexPQStats-members.html create mode 100644 docs/html/structfaiss_1_1IndexPQStats.html create mode 100644 docs/html/structfaiss_1_1IndexPreTransform-members.html create mode 100644 docs/html/structfaiss_1_1IndexPreTransform.html create mode 100644 docs/html/structfaiss_1_1IndexPreTransform.png create mode 100644 docs/html/structfaiss_1_1IndexRefineFlat-members.html create mode 100644 docs/html/structfaiss_1_1IndexRefineFlat.html create mode 100644 docs/html/structfaiss_1_1IndexRefineFlat.png create mode 100644 docs/html/structfaiss_1_1IndexShards-members.html create mode 100644 docs/html/structfaiss_1_1IndexShards.html create mode 100644 docs/html/structfaiss_1_1IndexShards.png create mode 100644 docs/html/structfaiss_1_1IndexSplitVectors-members.html create mode 100644 docs/html/structfaiss_1_1IndexSplitVectors.html create mode 100644 docs/html/structfaiss_1_1IndexSplitVectors.png create mode 100644 docs/html/structfaiss_1_1IndirectSort-members.html create mode 100644 docs/html/structfaiss_1_1IndirectSort.html create mode 100644 docs/html/structfaiss_1_1IntersectionCriterion-members.html create mode 100644 docs/html/structfaiss_1_1IntersectionCriterion.html create mode 100644 docs/html/structfaiss_1_1IntersectionCriterion.png create mode 100644 docs/html/structfaiss_1_1LinearTransform-members.html create mode 100644 docs/html/structfaiss_1_1LinearTransform.html create mode 100644 docs/html/structfaiss_1_1LinearTransform.png create mode 100644 docs/html/structfaiss_1_1MinSumK-members.html create mode 100644 docs/html/structfaiss_1_1MinSumK.html create mode 100644 docs/html/structfaiss_1_1MultiIndexQuantizer-members.html create mode 100644 docs/html/structfaiss_1_1MultiIndexQuantizer.html create mode 100644 docs/html/structfaiss_1_1MultiIndexQuantizer.png create mode 100644 docs/html/structfaiss_1_1NopDistanceCorrection-members.html create mode 100644 docs/html/structfaiss_1_1NopDistanceCorrection.html create mode 100644 docs/html/structfaiss_1_1OPQMatrix-members.html create mode 100644 docs/html/structfaiss_1_1OPQMatrix.html create mode 100644 docs/html/structfaiss_1_1OPQMatrix.png create mode 100644 docs/html/structfaiss_1_1OneRecallAtRCriterion-members.html create mode 100644 docs/html/structfaiss_1_1OneRecallAtRCriterion.html create mode 100644 docs/html/structfaiss_1_1OneRecallAtRCriterion.png create mode 100644 docs/html/structfaiss_1_1OperatingPoint-members.html create mode 100644 docs/html/structfaiss_1_1OperatingPoint.html create mode 100644 docs/html/structfaiss_1_1OperatingPoints-members.html create mode 100644 docs/html/structfaiss_1_1OperatingPoints.html create mode 100644 docs/html/structfaiss_1_1PCAMatrix-members.html create mode 100644 docs/html/structfaiss_1_1PCAMatrix.html create mode 100644 docs/html/structfaiss_1_1PCAMatrix.png create mode 100644 docs/html/structfaiss_1_1ParameterRange-members.html create mode 100644 docs/html/structfaiss_1_1ParameterRange.html create mode 100644 docs/html/structfaiss_1_1ParameterSpace-members.html create mode 100644 docs/html/structfaiss_1_1ParameterSpace.html create mode 100644 docs/html/structfaiss_1_1ParameterSpace.png create mode 100644 docs/html/structfaiss_1_1PermutationObjective-members.html create mode 100644 docs/html/structfaiss_1_1PermutationObjective.html create mode 100644 docs/html/structfaiss_1_1PermutationObjective.png create mode 100644 docs/html/structfaiss_1_1PolysemousTraining-members.html create mode 100644 docs/html/structfaiss_1_1PolysemousTraining.html create mode 100644 docs/html/structfaiss_1_1PolysemousTraining.png create mode 100644 docs/html/structfaiss_1_1ProductQuantizer-members.html create mode 100644 docs/html/structfaiss_1_1ProductQuantizer.html create mode 100644 docs/html/structfaiss_1_1RandomGenerator-members.html create mode 100644 docs/html/structfaiss_1_1RandomGenerator.html create mode 100644 docs/html/structfaiss_1_1RandomRotationMatrix-members.html create mode 100644 docs/html/structfaiss_1_1RandomRotationMatrix.html create mode 100644 docs/html/structfaiss_1_1RandomRotationMatrix.png create mode 100644 docs/html/structfaiss_1_1RangeSearchPartialResult-members.html create mode 100644 docs/html/structfaiss_1_1RangeSearchPartialResult.html create mode 100644 docs/html/structfaiss_1_1RangeSearchPartialResult.png create mode 100644 docs/html/structfaiss_1_1RangeSearchPartialResult_1_1QueryResult-members.html create mode 100644 docs/html/structfaiss_1_1RangeSearchPartialResult_1_1QueryResult.html create mode 100644 docs/html/structfaiss_1_1RangeSearchResult-members.html create mode 100644 docs/html/structfaiss_1_1RangeSearchResult.html create mode 100644 docs/html/structfaiss_1_1RankingScore2-members.html create mode 100644 docs/html/structfaiss_1_1RankingScore2.html create mode 100644 docs/html/structfaiss_1_1RankingScore2.png create mode 100644 docs/html/structfaiss_1_1RemapDimensionsTransform-members.html create mode 100644 docs/html/structfaiss_1_1RemapDimensionsTransform.html create mode 100644 docs/html/structfaiss_1_1RemapDimensionsTransform.png create mode 100644 docs/html/structfaiss_1_1ReproduceDistancesObjective-members.html create mode 100644 docs/html/structfaiss_1_1ReproduceDistancesObjective.html create mode 100644 docs/html/structfaiss_1_1ReproduceDistancesObjective.png create mode 100644 docs/html/structfaiss_1_1Score3Computer-members.html create mode 100644 docs/html/structfaiss_1_1Score3Computer.html create mode 100644 docs/html/structfaiss_1_1Score3Computer.png create mode 100644 docs/html/structfaiss_1_1SemiSortedArray-members.html create mode 100644 docs/html/structfaiss_1_1SemiSortedArray.html create mode 100644 docs/html/structfaiss_1_1SimulatedAnnealingOptimizer-members.html create mode 100644 docs/html/structfaiss_1_1SimulatedAnnealingOptimizer.html create mode 100644 docs/html/structfaiss_1_1SimulatedAnnealingOptimizer.png create mode 100644 docs/html/structfaiss_1_1SimulatedAnnealingParameters-members.html create mode 100644 docs/html/structfaiss_1_1SimulatedAnnealingParameters.html create mode 100644 docs/html/structfaiss_1_1SimulatedAnnealingParameters.png create mode 100644 docs/html/structfaiss_1_1SortedArray-members.html create mode 100644 docs/html/structfaiss_1_1SortedArray.html create mode 100644 docs/html/structfaiss_1_1VectorTransform-members.html create mode 100644 docs/html/structfaiss_1_1VectorTransform.html create mode 100644 docs/html/structfaiss_1_1VectorTransform.png create mode 100644 docs/html/structfaiss_1_1gpu_1_1BitonicMergeStep.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BitonicMergeStep_3_01K_00_01V_00_011_00_01Dir_00_01Comp_00_01Low_00_01true_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BitonicMergeStep_3_01K_00_01V_00_011_00_01Dir_00_01Comp_00_01Low_00_01true_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BitonicMergeStep_3_01K_00_01V_00_01N_00_01Dir_00_01Comp_00_01Low_00_01true_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BitonicMergeStep_3_01K_00_01V_00_01N_00_01Dir_00_01Comp_00_01Low_00_01true_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BitonicMergeStep_3_01K_00_01V_00_01N_00_01Dir_00_01Comp_00_01false_00_01false_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BitonicMergeStep_3_01K_00_01V_00_01N_00_01Dir_00_01Comp_00_01false_00_01false_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BitonicMergeStep_3_01K_00_01V_00_01N_00_01Dir_00_01Comp_00_01true_00_01false_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BitonicMergeStep_3_01K_00_01V_00_01N_00_01Dir_00_01Comp_00_01true_00_01false_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BitonicSortStep-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BitonicSortStep.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BitonicSortStep_3_01K_00_01V_00_011_00_01Dir_00_01Comp_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BitonicSortStep_3_01K_00_01V_00_011_00_01Dir_00_01Comp_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BlockMerge.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BlockMerge_3_01NumThreads_00_01K_00_01V_00_01N_00_01L_00_01Dir_00_01Comp_00_01false_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BlockMerge_3_01NumThreads_00_01K_00_01V_00_01N_00_01L_00_01Dir_00_01Comp_00_01false_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BlockMerge_3_01NumThreads_00_01K_00_01V_00_01N_00_01L_00_01Dir_00_01Comp_00_01true_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BlockMerge_3_01NumThreads_00_01K_00_01V_00_01N_00_01L_00_01Dir_00_01Comp_00_01true_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BlockSelect-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BlockSelect.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BlockSelect_3_01K_00_01V_00_01Dir_00_01Comp_00_011_00_01NumThreadQ_00_01ThreadsPerBlock_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1BlockSelect_3_01K_00_01V_00_01Dir_00_01Comp_00_011_00_01NumThreadQ_00_01ThreadsPerBlock_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Comparator-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Comparator.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1ConvertTo.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1ConvertTo_3_01float2_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1ConvertTo_3_01float2_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1ConvertTo_3_01float4_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1ConvertTo_3_01float4_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1ConvertTo_3_01float_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1ConvertTo_3_01float_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Converter.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Converter_3_01float_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Converter_3_01float_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1CublasGemm.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1CublasGemm_3_01float_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1CublasGemm_3_01float_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1FinalBlockMerge.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1FinalBlockMerge_3_011_00_01NumThreads_00_01K_00_01V_00_01NumWarpQ_00_01Dir_00_01Comp_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1FinalBlockMerge_3_011_00_01NumThreads_00_01K_00_01V_00_01NumWarpQ_00_01Dir_00_01Comp_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1FinalBlockMerge_3_012_00_01NumThreads_00_01K_00_01V_00_01NumWarpQ_00_01Dir_00_01Comp_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1FinalBlockMerge_3_012_00_01NumThreads_00_01K_00_01V_00_01NumWarpQ_00_01Dir_00_01Comp_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1FinalBlockMerge_3_014_00_01NumThreads_00_01K_00_01V_00_01NumWarpQ_00_01Dir_00_01Comp_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1FinalBlockMerge_3_014_00_01NumThreads_00_01K_00_01V_00_01NumWarpQ_00_01Dir_00_01Comp_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1FinalBlockMerge_3_018_00_01NumThreads_00_01K_00_01V_00_01NumWarpQ_00_01Dir_00_01Comp_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1FinalBlockMerge_3_018_00_01NumThreads_00_01K_00_01V_00_01NumWarpQ_00_01Dir_00_01Comp_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1GpuClonerOptions-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1GpuClonerOptions.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1GpuClonerOptions.png create mode 100644 docs/html/structfaiss_1_1gpu_1_1GpuMultipleClonerOptions-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1GpuMultipleClonerOptions.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1GpuMultipleClonerOptions.png create mode 100644 docs/html/structfaiss_1_1gpu_1_1GpuParameterSpace-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1GpuParameterSpace.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1GpuParameterSpace.png create mode 100644 docs/html/structfaiss_1_1gpu_1_1IVFFlatScan.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1IVFFlatScan_3-1_00_01L2_00_01T_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1IVFFlatScan_3-1_00_01L2_00_01T_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1IVFFlatScan_3_010_00_01L2_00_01T_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1IVFFlatScan_3_010_00_01L2_00_01T_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1IVFFlatScan_3_01128_00_01L2_00_01float_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1IVFFlatScan_3_01128_00_01L2_00_01float_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1IVFFlatScan_3_01256_00_01L2_00_01float_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1IVFFlatScan_3_01256_00_01L2_00_01float_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1IVFFlatScan_3_0164_00_01L2_00_01float_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1IVFFlatScan_3_0164_00_01L2_00_01float_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1IndexWrapper-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1IndexWrapper.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1IntToLong-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1IntToLong.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Limits.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Limits_3_01Pair_3_01K_00_01V_01_4_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Limits_3_01Pair_3_01K_00_01V_01_4_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Limits_3_01float_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Limits_3_01float_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Limits_3_01int_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Limits_3_01int_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0112_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0112_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0116_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0116_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_011_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_011_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0120_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0120_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0124_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0124_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0128_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0128_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_012_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_012_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0132_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0132_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_013_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_013_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0140_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0140_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0148_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0148_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_014_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_014_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0156_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0156_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0164_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_0164_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_018_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCode32_3_018_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCodeDistances-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadCodeDistances.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadStore-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1LoadStore.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Math-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Math.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Math_3_01float2_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Math_3_01float2_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Math_3_01float4_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Math_3_01float4_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Max-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Max.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Min-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Min.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Pair-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Pair.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1SegmentedReduce-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1SegmentedReduce.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1StackDeviceMemory_1_1Range-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1StackDeviceMemory_1_1Range.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1StackDeviceMemory_1_1Stack-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1StackDeviceMemory_1_1Stack.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Sum-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1Sum.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1TensorInfo-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1TensorInfo.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1TensorInfoOffset-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1TensorInfoOffset.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1TensorInfoOffset_3_01T_00-1_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1TensorInfoOffset_3_01T_00-1_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1ToCPUCloner-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1ToCPUCloner.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1ToCPUCloner.png create mode 100644 docs/html/structfaiss_1_1gpu_1_1ToGpuCloner-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1ToGpuCloner.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1ToGpuCloner.png create mode 100644 docs/html/structfaiss_1_1gpu_1_1ToGpuClonerMultiple-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1ToGpuClonerMultiple.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1ToGpuClonerMultiple.png create mode 100644 docs/html/structfaiss_1_1gpu_1_1WarpSelect-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1WarpSelect.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1WarpSelect_3_01K_00_01V_00_01Dir_00_01Comp_00_011_00_01NumThreadQ_00_01ThreadsPerBlock_01_4-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1WarpSelect_3_01K_00_01V_00_01Dir_00_01Comp_00_011_00_01NumThreadQ_00_01ThreadsPerBlock_01_4.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1traits_1_1DefaultPtrTraits-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1traits_1_1DefaultPtrTraits.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1traits_1_1RestrictPtrTraits-members.html create mode 100644 docs/html/structfaiss_1_1gpu_1_1traits_1_1RestrictPtrTraits.html create mode 100644 docs/html/sync_off.png create mode 100644 docs/html/sync_on.png create mode 100644 docs/html/tab_a.png create mode 100644 docs/html/tab_b.png create mode 100644 docs/html/tab_h.png create mode 100644 docs/html/tab_s.png create mode 100644 docs/html/tabs.css create mode 100644 docs/html/test__blas_8cpp_source.html create mode 100644 docs/html/test__ivfpq__indexing_8cpp_source.html create mode 100644 docs/html/utils_8cpp_source.html create mode 100644 docs/html/utils_8h_source.html create mode 100644 example_makefiles/makefile.inc.Linux create mode 100644 example_makefiles/makefile.inc.Mac.brew create mode 100644 example_makefiles/makefile.inc.Mac.port create mode 100644 faiss.h create mode 100644 faiss.py create mode 100644 gpu/GpuAutoTune.cpp create mode 100644 gpu/GpuAutoTune.h create mode 100644 gpu/GpuIndex.cu create mode 100644 gpu/GpuIndex.h create mode 100644 gpu/GpuIndexFlat.cu create mode 100644 gpu/GpuIndexFlat.h create mode 100644 gpu/GpuIndexIVF.cu create mode 100644 gpu/GpuIndexIVF.h create mode 100644 gpu/GpuIndexIVFFlat.cu create mode 100644 gpu/GpuIndexIVFFlat.h create mode 100644 gpu/GpuIndexIVFPQ.cu create mode 100644 gpu/GpuIndexIVFPQ.h create mode 100644 gpu/GpuIndicesOptions.h create mode 100644 gpu/GpuResources.cpp create mode 100644 gpu/GpuResources.h create mode 100644 gpu/IndexProxy.cpp create mode 100644 gpu/IndexProxy.h create mode 100644 gpu/Makefile create mode 100644 gpu/StandardGpuResources.cpp create mode 100644 gpu/StandardGpuResources.h create mode 100644 gpu/impl/BroadcastSum.cu create mode 100644 gpu/impl/BroadcastSum.cuh create mode 100644 gpu/impl/Distance.cu create mode 100644 gpu/impl/Distance.cuh create mode 100644 gpu/impl/FlatIndex.cu create mode 100644 gpu/impl/FlatIndex.cuh create mode 100644 gpu/impl/IVFBase.cu create mode 100644 gpu/impl/IVFBase.cuh create mode 100644 gpu/impl/IVFFlat.cu create mode 100644 gpu/impl/IVFFlat.cuh create mode 100644 gpu/impl/IVFFlatScan.cu create mode 100644 gpu/impl/IVFFlatScan.cuh create mode 100644 gpu/impl/IVFPQ.cu create mode 100644 gpu/impl/IVFPQ.cuh create mode 100644 gpu/impl/IVFUtils.cu create mode 100644 gpu/impl/IVFUtils.cuh create mode 100644 gpu/impl/IVFUtilsSelect1.cu create mode 100644 gpu/impl/IVFUtilsSelect2.cu create mode 100644 gpu/impl/InvertedListAppend.cu create mode 100644 gpu/impl/InvertedListAppend.cuh create mode 100644 gpu/impl/L2Norm.cu create mode 100644 gpu/impl/L2Norm.cuh create mode 100644 gpu/impl/L2Select.cu create mode 100644 gpu/impl/L2Select.cuh create mode 100644 gpu/impl/PQCodeDistances.cu create mode 100644 gpu/impl/PQCodeDistances.cuh create mode 100644 gpu/impl/PQCodeLoad.cuh create mode 100644 gpu/impl/PQScanMultiPassNoPrecomputed.cu create mode 100644 gpu/impl/PQScanMultiPassNoPrecomputed.cuh create mode 100644 gpu/impl/PQScanMultiPassPrecomputed.cu create mode 100644 gpu/impl/PQScanMultiPassPrecomputed.cuh create mode 100644 gpu/impl/RemapIndices.cpp create mode 100644 gpu/impl/RemapIndices.h create mode 100644 gpu/impl/VectorResidual.cu create mode 100644 gpu/impl/VectorResidual.cuh create mode 100644 gpu/perf/CompareFlat.cu create mode 100644 gpu/perf/CompareIVFFlat.cu create mode 100644 gpu/perf/CompareIVFPQ.cu create mode 100644 gpu/perf/CompareIVFPQGrid.cu create mode 100644 gpu/perf/IndexWrapper-inl.h create mode 100644 gpu/perf/IndexWrapper.h create mode 100644 gpu/perf/PerfClustering.cpp create mode 100644 gpu/perf/PerfIVFPQAdd.cpp create mode 100644 gpu/perf/WriteIndex.cpp create mode 100644 gpu/test/TestGpuIndexFlat.cpp create mode 100644 gpu/test/TestGpuIndexIVFFlat.cpp create mode 100644 gpu/test/TestGpuIndexIVFPQ.cpp create mode 100644 gpu/test/TestGpuSelect.cu create mode 100644 gpu/test/TestUtils.cpp create mode 100644 gpu/test/TestUtils.h create mode 100644 gpu/test/demo_ivfpq_indexing_gpu.cpp create mode 100644 gpu/test/test_gpu_index.py create mode 100644 gpu/utils/BlockSelectFloat.cu create mode 100644 gpu/utils/BlockSelectHalf.cu create mode 100644 gpu/utils/BlockSelectKernel.cuh create mode 100644 gpu/utils/Comparators.cuh create mode 100644 gpu/utils/ConversionOperators.cuh create mode 100644 gpu/utils/CopyUtils.cuh create mode 100644 gpu/utils/DeviceDefs.cuh create mode 100644 gpu/utils/DeviceMemory.cpp create mode 100644 gpu/utils/DeviceMemory.h create mode 100644 gpu/utils/DeviceTensor-inl.cuh create mode 100644 gpu/utils/DeviceTensor.cuh create mode 100644 gpu/utils/DeviceUtils.cpp create mode 100644 gpu/utils/DeviceUtils.h create mode 100644 gpu/utils/DeviceVector.cuh create mode 100644 gpu/utils/Float16.cu create mode 100644 gpu/utils/Float16.cuh create mode 100644 gpu/utils/HostTensor-inl.cuh create mode 100644 gpu/utils/HostTensor.cuh create mode 100644 gpu/utils/Limits.cuh create mode 100644 gpu/utils/LoadStoreOperators.cuh create mode 100644 gpu/utils/MathOperators.cuh create mode 100644 gpu/utils/MatrixMult.cu create mode 100644 gpu/utils/MatrixMult.cuh create mode 100644 gpu/utils/MergeNetworkBlock.cuh create mode 100644 gpu/utils/MergeNetworkWarp.cuh create mode 100644 gpu/utils/NoTypeTensor.cuh create mode 100644 gpu/utils/Pair.cuh create mode 100644 gpu/utils/PtxUtils.cuh create mode 100644 gpu/utils/ReductionOperators.cuh create mode 100644 gpu/utils/Reductions.cuh create mode 100644 gpu/utils/Select.cuh create mode 100644 gpu/utils/StackDeviceMemory.cpp create mode 100644 gpu/utils/StackDeviceMemory.h create mode 100644 gpu/utils/StaticUtils.h create mode 100644 gpu/utils/Tensor-inl.cuh create mode 100644 gpu/utils/Tensor.cuh create mode 100644 gpu/utils/ThrustAllocator.cuh create mode 100644 gpu/utils/Timer.cpp create mode 100644 gpu/utils/Timer.h create mode 100644 gpu/utils/Transpose.cuh create mode 100644 gpu/utils/WarpSelectFloat.cu create mode 100644 gpu/utils/WarpSelectHalf.cu create mode 100644 gpu/utils/WarpSelectKernel.cuh create mode 100644 gpu/utils/WarpShuffles.cuh create mode 100644 gpu/utils/WorkerThread.cpp create mode 100644 gpu/utils/WorkerThread.h create mode 100644 gpu/utils/blockselect/BlockSelectFloat1.cu create mode 100644 gpu/utils/blockselect/BlockSelectFloat128.cu create mode 100644 gpu/utils/blockselect/BlockSelectFloat256.cu create mode 100644 gpu/utils/blockselect/BlockSelectFloat32.cu create mode 100644 gpu/utils/blockselect/BlockSelectFloat64.cu create mode 100644 gpu/utils/blockselect/BlockSelectFloatF1024.cu create mode 100644 gpu/utils/blockselect/BlockSelectFloatF512.cu create mode 100644 gpu/utils/blockselect/BlockSelectFloatT1024.cu create mode 100644 gpu/utils/blockselect/BlockSelectFloatT512.cu create mode 100644 gpu/utils/blockselect/BlockSelectHalf1.cu create mode 100644 gpu/utils/blockselect/BlockSelectHalf128.cu create mode 100644 gpu/utils/blockselect/BlockSelectHalf256.cu create mode 100644 gpu/utils/blockselect/BlockSelectHalf32.cu create mode 100644 gpu/utils/blockselect/BlockSelectHalf64.cu create mode 100644 gpu/utils/blockselect/BlockSelectHalfF1024.cu create mode 100644 gpu/utils/blockselect/BlockSelectHalfF512.cu create mode 100644 gpu/utils/blockselect/BlockSelectHalfT1024.cu create mode 100644 gpu/utils/blockselect/BlockSelectHalfT512.cu create mode 100644 gpu/utils/blockselect/BlockSelectImpl.cuh create mode 100644 gpu/utils/warpselect/WarpSelectFloat1.cu create mode 100644 gpu/utils/warpselect/WarpSelectFloat128.cu create mode 100644 gpu/utils/warpselect/WarpSelectFloat256.cu create mode 100644 gpu/utils/warpselect/WarpSelectFloat32.cu create mode 100644 gpu/utils/warpselect/WarpSelectFloat64.cu create mode 100644 gpu/utils/warpselect/WarpSelectFloatF1024.cu create mode 100644 gpu/utils/warpselect/WarpSelectFloatF512.cu create mode 100644 gpu/utils/warpselect/WarpSelectFloatT1024.cu create mode 100644 gpu/utils/warpselect/WarpSelectFloatT512.cu create mode 100644 gpu/utils/warpselect/WarpSelectHalf1.cu create mode 100644 gpu/utils/warpselect/WarpSelectHalf128.cu create mode 100644 gpu/utils/warpselect/WarpSelectHalf256.cu create mode 100644 gpu/utils/warpselect/WarpSelectHalf32.cu create mode 100644 gpu/utils/warpselect/WarpSelectHalf64.cu create mode 100644 gpu/utils/warpselect/WarpSelectHalfF1024.cu create mode 100644 gpu/utils/warpselect/WarpSelectHalfF512.cu create mode 100644 gpu/utils/warpselect/WarpSelectHalfT1024.cu create mode 100644 gpu/utils/warpselect/WarpSelectHalfT512.cu create mode 100644 gpu/utils/warpselect/WarpSelectImpl.cuh create mode 100644 hamming.cpp create mode 100644 hamming.h create mode 100644 index_io.cpp create mode 100644 index_io.h create mode 100644 python/demo_auto_tune.py create mode 100644 python/demo_ivfpq.py create mode 100644 python/swigfaiss.py create mode 100644 python/swigfaiss_gpu.py create mode 100644 python/swigfaiss_gpu_wrap.cxx create mode 100644 python/swigfaiss_wrap.cxx create mode 100644 python/test_build_blocks.py create mode 100644 python/test_index.py create mode 100644 swigfaiss.swig create mode 100644 tests/demo_imi_flat.cpp create mode 100644 tests/demo_ivfpq_indexing.cpp create mode 100644 tests/demo_sift1M.cpp create mode 100644 tests/test_blas.cpp create mode 100644 tests/test_ivfpq_indexing.cpp create mode 100644 tutorial/cpp/1-Flat.cpp create mode 100644 tutorial/cpp/2-IVFFlat.cpp create mode 100644 tutorial/cpp/3-IVFPQ.cpp create mode 100644 tutorial/python/1-Flat.py create mode 100644 tutorial/python/2-IVFFlat.py create mode 100644 tutorial/python/3-IVFPQ.py create mode 100644 utils.cpp create mode 100644 utils.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..0af5fcaf0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +/makefile.inc +/tests/demo_ivfpq_indexing +*.swp +*.swo +*.o +*.a +*.dSYM +*.so +*.pyc +*~ diff --git a/AutoTune.cpp b/AutoTune.cpp new file mode 100644 index 000000000..d594fbf00 --- /dev/null +++ b/AutoTune.cpp @@ -0,0 +1,776 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +/* Copyright 2004-present Facebook. All Rights Reserved. + implementation of Hyper-parameter auto-tuning +*/ + +#include "AutoTune.h" + +#include "FaissAssert.h" +#include "utils.h" + +#include "IndexFlat.h" +#include "VectorTransform.h" +#include "IndexLSH.h" +#include "IndexPQ.h" +#include "IndexIVF.h" +#include "IndexIVFPQ.h" +#include "MetaIndexes.h" + + + +namespace faiss { + + +AutoTuneCriterion::AutoTuneCriterion (idx_t nq, idx_t nnn): + nq (nq), nnn (nnn), gt_nnn (0) +{} + + +void AutoTuneCriterion::set_groundtruth ( + int gt_nnn, const float *gt_D_in, const idx_t *gt_I_in) +{ + this->gt_nnn = gt_nnn; + if (gt_D_in) { // allow null for this, as it is often not used + gt_D.resize (nq * gt_nnn); + memcpy (gt_D.data(), gt_D_in, sizeof (gt_D[0]) * nq * gt_nnn); + } + gt_I.resize (nq * gt_nnn); + memcpy (gt_I.data(), gt_I_in, sizeof (gt_I[0]) * nq * gt_nnn); +} + + + +OneRecallAtRCriterion::OneRecallAtRCriterion (idx_t nq, idx_t R): + AutoTuneCriterion(nq, R), R(R) +{} + +double OneRecallAtRCriterion::evaluate (const float *D, const idx_t *I) const +{ + FAISS_ASSERT ((gt_I.size() == gt_nnn * nq && gt_nnn >= 1 && nnn >= R) || + !"gound truth not initialized"); + idx_t n_ok = 0; + for (idx_t q = 0; q < nq; q++) { + idx_t gt_nn = gt_I [q * gt_nnn]; + const idx_t *I_line = I + q * nnn; + for (int i = 0; i < R; i++) { + if (I_line[i] == gt_nn) { + n_ok++; + break; + } + } + } + return n_ok / double (nq); +} + + +IntersectionCriterion::IntersectionCriterion (idx_t nq, idx_t R): + AutoTuneCriterion(nq, R), R(R) +{} + +double IntersectionCriterion::evaluate (const float *D, const idx_t *I) const +{ + FAISS_ASSERT ((gt_I.size() == gt_nnn * nq && gt_nnn >= R && nnn >= R) || + !"gound truth not initialized"); + long n_ok = 0; +#pragma omp parallel for reduction(+: n_ok) + for (idx_t q = 0; q < nq; q++) { + n_ok += ranklist_intersection_size ( + R, >_I [q * gt_nnn], + R, I + q * nnn); + } + return n_ok / double (nq * R); +} + +/*************************************************************** + * OperatingPoints + ***************************************************************/ + +OperatingPoints::OperatingPoints () +{ + clear(); +} + +void OperatingPoints::clear () +{ + all_pts.clear(); + optimal_pts.clear(); + /// default point: doing nothing gives 0 performance and takes 0 time + OperatingPoint op = {0, 0, "", -1}; + optimal_pts.push_back(op); +} + +/// add a performance measure +bool OperatingPoints::add (double perf, double t, const std::string & key, + size_t cno) +{ + OperatingPoint op = {perf, t, key, long(cno)}; + all_pts.push_back (op); + if (perf == 0) { + return false; // no method for 0 accuracy is faster than doing nothing + } + std::vector & a = optimal_pts; + if (perf > a.back().perf) { + // keep unconditionally + a.push_back (op); + } else if (perf == a.back().perf) { + if (t < a.back ().t) { + a.back() = op; + } else { + return false; + } + } else { + int i; + // stricto sensu this should be a bissection + for (i = 0; i < a.size(); i++) { + if (a[i].perf >= perf) break; + } + assert (i < a.size()); + if (t < a[i].t) { + if (a[i].perf == perf) { + a[i] = op; + } else { + a.insert (a.begin() + i, op); + } + } else { + return false; + } + } + { // remove non-optimal points from array + int i = a.size() - 1; + while (i > 0) { + if (a[i].t < a[i - 1].t) + a.erase (a.begin() + (i - 1)); + i--; + } + } + return true; +} + + +int OperatingPoints::merge_with (const OperatingPoints &other, + const std::string & prefix) +{ + int n_add = 0; + for (int i = 0; i < other.all_pts.size(); i++) { + const OperatingPoint & op = other.all_pts[i]; + if (add (op.perf, op.t, prefix + op.key, op.cno)) + n_add++; + } + return n_add; +} + + + +/// get time required to obtain a given performance measure +double OperatingPoints::t_for_perf (double perf) const +{ + const std::vector & a = optimal_pts; + if (perf > a.back().perf) return 1e50; + int i0 = -1, i1 = a.size() - 1; + while (i0 + 1 < i1) { + int imed = (i0 + i1 + 1) / 2; + if (a[imed].perf < perf) i0 = imed; + else i1 = imed; + } + return a[i1].t; +} + + +void OperatingPoints::all_to_gnuplot (const char *fname) const +{ + FILE *f = fopen(fname, "w"); + if (!f) { + fprintf (stderr, "cannot open %s", fname); + perror(""); + abort(); + } + for (int i = 0; i < all_pts.size(); i++) { + const OperatingPoint & op = all_pts[i]; + fprintf (f, "%g %g %s\n", op.perf, op.t, op.key.c_str()); + } + fclose(f); +} + +void OperatingPoints::optimal_to_gnuplot (const char *fname) const +{ + FILE *f = fopen(fname, "w"); + if (!f) { + fprintf (stderr, "cannot open %s", fname); + perror(""); + abort(); + } + double prev_perf = 0.0; + for (int i = 0; i < optimal_pts.size(); i++) { + const OperatingPoint & op = optimal_pts[i]; + fprintf (f, "%g %g\n", prev_perf, op.t); + fprintf (f, "%g %g %s\n", op.perf, op.t, op.key.c_str()); + prev_perf = op.perf; + } + fclose(f); +} + +void OperatingPoints::display (bool only_optimal) const +{ + const std::vector &pts = + only_optimal ? optimal_pts : all_pts; + printf("Tested %ld operating points, %ld ones are optimal:\n", + all_pts.size(), optimal_pts.size()); + + for (int i = 0; i < pts.size(); i++) { + const OperatingPoint & op = pts[i]; + const char *star = ""; + if (!only_optimal) { + for (int j = 0; j < optimal_pts.size(); j++) { + if (op.cno == optimal_pts[j].cno) { + star = "*"; + break; + } + } + } + printf ("cno=%ld key=%s perf=%.4f t=%.3f %s\n", + op.cno, op.key.c_str(), op.perf, op.t, star); + } + +} + +/*************************************************************** + * ParameterSpace + ***************************************************************/ + +ParameterSpace::ParameterSpace (): + verbose (1), n_experiments (500), + batchsize (1<<30), thread_over_batches (false) +{ +} + +/* not keeping this constructor as inheritors will call the parent + initialize() + */ + +#if 0 +ParameterSpace::ParameterSpace (Index *index): + verbose (1), n_experiments (500), + batchsize (1<<30), thread_over_batches (false) +{ + initialize(index); +} +#endif + +size_t ParameterSpace::n_combinations () const +{ + size_t n = 1; + for (int i = 0; i < parameter_ranges.size(); i++) + n *= parameter_ranges[i].values.size(); + return n; +} + +/// get string representation of the combination +std::string ParameterSpace::combination_name (size_t cno) const { + char buf[1000], *wp = buf; + *wp = 0; + for (int i = 0; i < parameter_ranges.size(); i++) { + const ParameterRange & pr = parameter_ranges[i]; + size_t j = cno % pr.values.size(); + cno /= pr.values.size(); + wp += snprintf ( + wp, buf + 1000 - wp, "%s%s=%g", i == 0 ? "" : ",", + pr.name.c_str(), pr.values[j]); + } + return std::string (buf); +} + + +bool ParameterSpace::combination_ge (size_t c1, size_t c2) const +{ + for (int i = 0; i < parameter_ranges.size(); i++) { + int nval = parameter_ranges[i].values.size(); + size_t j1 = c1 % nval; + size_t j2 = c2 % nval; + if (!(j1 >= j2)) return false; + c1 /= nval; + c2 /= nval; + } + return true; +} + + + +#define DC(classname) \ + const classname *ix = dynamic_cast(index) + +static void init_pq_ParameterRange (const ProductQuantizer & pq, + ParameterRange & pr) +{ + if (pq.code_size % 4 == 0) { + // Polysemous not supported for code sizes that are not a + // multiple of 4 + for (int i = 2; i <= pq.code_size * 8 / 2; i+= 2) + pr.values.push_back(i); + } + pr.values.push_back (pq.code_size * 8); +} + +ParameterRange &ParameterSpace::add_range(const char * name) +{ + parameter_ranges.push_back (ParameterRange ()); + parameter_ranges.back ().name = name; + return parameter_ranges.back (); +} + + +/// initialize with reasonable parameters for the index +void ParameterSpace::initialize (const Index * index) +{ + if (DC (IndexPreTransform)) { + index = ix->index; + } + if (DC (IndexRefineFlat)) { + ParameterRange & pr = add_range("k_factor_rf"); + for (int i = 0; i <= 6; i++) { + pr.values.push_back (1 << i); + } + index = ix->base_index; + } + if (DC (IndexPreTransform)) { + index = ix->index; + } + + if (DC (IndexIVF)) { + ParameterRange & pr = add_range("nprobe"); + for (int i = 0; i < 13; i++) { + size_t nprobe = 1 << i; + if (nprobe >= ix->nlist) break; + pr.values.push_back (nprobe); + } + } + if (DC (IndexPQ)) { + ParameterRange & pr = add_range("ht"); + init_pq_ParameterRange (ix->pq, pr); + } + if (DC (IndexIVFPQ)) { + ParameterRange & pr = add_range("ht"); + init_pq_ParameterRange (ix->pq, pr); + + const MultiIndexQuantizer *miq = + dynamic_cast (ix->quantizer); + if (miq) { + ParameterRange & pr_max_codes = add_range("max_codes"); + for (int i = 8; i < 20; i++) { + pr_max_codes.values.push_back (1 << i); + } + pr_max_codes.values.push_back (1.0 / 0.0); + } + } + if (DC (IndexIVFPQR)) { + assert (ix); + ParameterRange & pr = add_range("k_factor"); + for (int i = 0; i <= 6; i++) { + pr.values.push_back (1 << i); + } + } +} + +#undef DC + +// non-const version +#define DC(classname) classname *ix = dynamic_cast(index) + + +/// set a combination of parameters on an index +void ParameterSpace::set_index_parameters (Index *index, size_t cno) const +{ + + for (int i = 0; i < parameter_ranges.size(); i++) { + const ParameterRange & pr = parameter_ranges[i]; + size_t j = cno % pr.values.size(); + cno /= pr.values.size(); + double val = pr.values [j]; + set_index_parameter (index, pr.name, val); + } +} + +/// set a combination of parameters on an index +void ParameterSpace::set_index_parameters ( + Index *index, const char *description_in) const +{ + char description[strlen(description_in) + 1]; + char *ptr; + memcpy (description, description_in, strlen(description_in) + 1); + + for (char *tok = strtok_r (description, " ,", &ptr); + tok; + tok = strtok_r (nullptr, " ,", &ptr)) { + char name[100]; + double val; + FAISS_ASSERT (sscanf (tok, "%100[^=]=%lf", name, &val) == 2); + set_index_parameter (index, name, val); + } + +} + +void ParameterSpace::set_index_parameter ( + Index * index, const std::string & name, double val) const +{ + if (verbose > 1) + printf(" set %s=%g\n", name.c_str(), val); + + if (name == "verbose") { + index->verbose = int(val); + } + if (DC (IndexPreTransform)) { + index = ix->index; + } + if (name == "verbose") { + index->verbose = int(val); + } + if (DC (IndexRefineFlat)) { + if (name == "k_factor_rf") { + ix->k_factor = int(val); + return; + } + index = ix->base_index; + } + if (DC (IndexPreTransform)) { + index = ix->index; + } + if (name == "verbose") { + index->verbose = int(val); + return; // last verbose that we could find + } + if (name == "nprobe") { + DC(IndexIVF); + ix->nprobe = int(val); + } else if (name == "ht") { + if (DC (IndexPQ)) { + if (val >= ix->pq.code_size * 8) { + ix->search_type = IndexPQ::ST_PQ; + } else { + ix->search_type = IndexPQ::ST_polysemous; + ix->polysemous_ht = int(val); + } + } else if (DC (IndexIVFPQ)) { + if (val >= ix->pq.code_size * 8) { + ix->polysemous_ht = 0; + } else { + ix->polysemous_ht = int(val); + } + } + } else if (name == "k_factor") { + DC (IndexIVFPQR); + ix->k_factor = val; + } else if (name == "max_codes") { + DC (IndexIVFPQ); + ix->max_codes = finite(val) ? size_t(val) : 0; + } else { + fprintf(stderr, + "ParameterSpace::set_index_parameter:" + "could not set parameter %s\n", + name.c_str()); + } +} + +void ParameterSpace::display () const +{ + printf ("ParameterSpace, %ld parameters, %ld combinations:\n", + parameter_ranges.size (), n_combinations ()); + for (int i = 0; i < parameter_ranges.size(); i++) { + const ParameterRange & pr = parameter_ranges[i]; + printf (" %s: ", pr.name.c_str ()); + char sep = '['; + for (int j = 0; j < pr.values.size(); j++) { + printf ("%c %g", sep, pr.values [j]); + sep = ','; + } + printf ("]\n"); + } +} + + + +void ParameterSpace::update_bounds (size_t cno, const OperatingPoint & op, + double *upper_bound_perf, + double *lower_bound_t) const +{ + if (combination_ge (cno, op.cno)) { + if (op.t > *lower_bound_t) *lower_bound_t = op.t; + } + if (combination_ge (op.cno, cno)) { + if (op.perf < *upper_bound_perf) *upper_bound_perf = op.perf; + } +} + + + +void ParameterSpace::explore (Index *index, + size_t nq, const float *xq, + const AutoTuneCriterion & crit, + OperatingPoints * ops) const +{ + FAISS_ASSERT (nq == crit.nq || + !"criterion does not have the same nb of queries"); + + size_t n_comb = n_combinations (); + + if (n_experiments == 0) { + + for (size_t cno = 0; cno < n_comb; cno++) { + set_index_parameters (index, cno); + std::vector I(nq * crit.nnn); + std::vector D(nq * crit.nnn); + + double t0 = getmillisecs (); + index->search (nq, xq, crit.nnn, D.data(), I.data()); + double t_search = (getmillisecs() - t0) / 1e3; + + double perf = crit.evaluate (D.data(), I.data()); + + bool keep = ops->add (perf, t_search, combination_name (cno), cno); + + if (verbose) + printf(" %ld/%ld: %s perf=%.3f t=%.3f s %s\n", cno, n_comb, + combination_name (cno).c_str(), perf, t_search, + keep ? "*" : ""); + } + return; + } + + int n_exp = n_experiments; + + if (n_exp > n_comb) n_exp = n_comb; + FAISS_ASSERT (n_comb == 1 || n_exp > 2); + std::vector perm (n_comb); + // make sure the slowest and fastest experiment are run + perm[0] = 0; + if (n_comb > 1) { + perm[1] = n_comb - 1; + rand_perm (&perm[2], n_comb - 2, 1234); + for (int i = 2; i < perm.size(); i++) perm[i] ++; + } + + for (size_t xp = 0; xp < n_exp; xp++) { + size_t cno = perm[xp]; + + if (verbose) + printf(" %ld/%d: cno=%ld %s ", xp, n_exp, cno, + combination_name (cno).c_str()); + + { + double lower_bound_t = 0.0; + double upper_bound_perf = 1.0; + for (int i = 0; i < ops->all_pts.size(); i++) { + update_bounds (cno, ops->all_pts[i], + &upper_bound_perf, &lower_bound_t); + } + double best_t = ops->t_for_perf (upper_bound_perf); + if (verbose) + printf ("bounds [perf<=%.3f t>=%.3f] %s", + upper_bound_perf, lower_bound_t, + best_t <= lower_bound_t ? "skip\n" : ""); + if (best_t <= lower_bound_t) continue; + } + + set_index_parameters (index, cno); + std::vector I(nq * crit.nnn); + std::vector D(nq * crit.nnn); + + double t0 = getmillisecs (); + + if (thread_over_batches) { +#pragma omp parallel for + for (size_t q0 = 0; q0 < nq; q0 += batchsize) { + size_t q1 = q0 + batchsize; + if (q1 > nq) q1 = nq; + index->search (q1 - q0, xq + q0 * index->d, + crit.nnn, + D.data() + q0 * crit.nnn, + I.data() + q0 * crit.nnn); + } + } else { + for (size_t q0 = 0; q0 < nq; q0 += batchsize) { + size_t q1 = q0 + batchsize; + if (q1 > nq) q1 = nq; + index->search (q1 - q0, xq + q0 * index->d, + crit.nnn, + D.data() + q0 * crit.nnn, + I.data() + q0 * crit.nnn); + } + } + + double t_search = (getmillisecs() - t0) / 1e3; + + double perf = crit.evaluate (D.data(), I.data()); + + bool keep = ops->add (perf, t_search, combination_name (cno), cno); + + if (verbose) + printf(" perf %.3f t %.3f %s\n", perf, t_search, + keep ? "*" : ""); + } +} + +/*************************************************************** + * index_factory + ***************************************************************/ + +Index *index_factory (int d, const char *description_in, MetricType metric) +{ + VectorTransform *vt = nullptr; + Index *coarse_quantizer = nullptr; + Index *index = nullptr; + bool add_idmap = false; + bool make_IndexRefineFlat = false; + + char description[strlen(description_in) + 1]; + char *ptr; + memcpy (description, description_in, strlen(description_in) + 1); + + int ncentroids = -1; + + for (char *tok = strtok_r (description, " ,", &ptr); + tok; + tok = strtok_r (nullptr, " ,", &ptr)) { + int d_out, opq_M, nbit, M, M2; + VectorTransform *vt_1 = nullptr; + Index *coarse_quantizer_1 = nullptr; + Index *index_1 = nullptr; + + // VectorTransforms + if (sscanf (tok, "PCA%d", &d_out) == 1) { + vt_1 = new PCAMatrix (d, d_out); + d = d_out; + } else if (sscanf (tok, "PCAR%d", &d_out) == 1) { + vt_1 = new PCAMatrix (d, d_out, 0, true); + d = d_out; + } else if (sscanf (tok, "OPQ%d_%d", &opq_M, &d_out) == 2) { + vt_1 = new OPQMatrix (d, opq_M, d_out); + d = d_out; + } else if (sscanf (tok, "OPQ%d", &opq_M) == 1) { + vt_1 = new OPQMatrix (d, opq_M); + // coarse quantizers + } else if (sscanf (tok, "IVF%d", &ncentroids) == 1) { + if (metric == METRIC_L2) { + coarse_quantizer_1 = new IndexFlatL2 (d); + } else { // if (metric == METRIC_IP) + coarse_quantizer_1 = new IndexFlatIP (d); + } + } else if (sscanf (tok, "IMI2x%d", &nbit) == 1) { + FAISS_ASSERT(metric == METRIC_L2 || + !"MultiIndex not implemented for inner prod search"); + coarse_quantizer_1 = new MultiIndexQuantizer (d, 2, nbit); + ncentroids = 1 << (2 * nbit); + } else if (strcmp(tok, "IDMap") == 0) { + add_idmap = true; + + // IVFs + } else if (strcmp (tok, "Flat") == 0) { + if (coarse_quantizer) { + // if there was an IVF in front, then it is an IVFFlat + IndexIVF *index_ivf = new IndexIVFFlat ( + coarse_quantizer, d, ncentroids, metric); + index_ivf->quantizer_trains_alone = + dynamic_cast(coarse_quantizer) + != nullptr; + index_ivf->cp.spherical = metric == METRIC_INNER_PRODUCT; + index_ivf->own_fields = true; + index_1 = index_ivf; + } else { + index_1 = new IndexFlat (d, metric); + if (add_idmap) { + IndexIDMap *idmap = new IndexIDMap(index_1); + idmap->own_fields = true; + index_1 = idmap; + add_idmap = false; + } + } + } else if (sscanf (tok, "PQ%d+%d", &M, &M2) == 2) { + FAISS_ASSERT(coarse_quantizer || + !"PQ with + works only with an IVF"); + FAISS_ASSERT(metric == METRIC_L2 || + !"IVFPQR not implemented for inner product search"); + IndexIVFPQR *index_ivf = new IndexIVFPQR ( + coarse_quantizer, d, ncentroids, M, 8, M2, 8); + index_ivf->quantizer_trains_alone = + dynamic_cast(coarse_quantizer) + != nullptr; + index_ivf->own_fields = true; + index_1 = index_ivf; + } else if (sscanf (tok, "PQ%d", &M) == 1) { + if (coarse_quantizer) { + IndexIVFPQ *index_ivf = new IndexIVFPQ ( + coarse_quantizer, d, ncentroids, M, 8); + index_ivf->quantizer_trains_alone = + dynamic_cast(coarse_quantizer) + != nullptr; + index_ivf->metric_type = metric; + index_ivf->cp.spherical = metric == METRIC_INNER_PRODUCT; + index_ivf->own_fields = true; + index_ivf->do_polysemous_training = true; + index_1 = index_ivf; + } else { + IndexPQ *index_pq = new IndexPQ (d, M, 8, metric); + index_pq->do_polysemous_training = true; + index_1 = index_pq; + if (add_idmap) { + IndexIDMap *idmap = new IndexIDMap(index_1); + idmap->own_fields = true; + index_1 = idmap; + add_idmap = false; + } + } + } else if (strcmp (tok, "RFlat") == 0) { + make_IndexRefineFlat = true; + } else { + fprintf (stderr, "could not parse token \"%s\" in %s\n", + tok, description_in); + FAISS_ASSERT (!"parse error"); + } + + if (vt_1) { + FAISS_ASSERT (!vt || !"cannot apply two VectorTransforms"); + vt = vt_1; + } + + if (coarse_quantizer_1) { + FAISS_ASSERT (!coarse_quantizer || + !"cannot have 2 coarse quantizers"); + coarse_quantizer = coarse_quantizer_1; + } + + if (index_1) { + FAISS_ASSERT (!index || !"cannot have 2 indexes"); + index = index_1; + } + } + + if (add_idmap) { + fprintf(stderr, "index_factory: WARNING: " + "IDMap option not used\n"); + } + + if (vt) { + IndexPreTransform *index_pt = new IndexPreTransform (vt, index); + index_pt->own_fields = true; + index = index_pt; + } + + if (make_IndexRefineFlat) { + IndexRefineFlat *index_rf = new IndexRefineFlat (index); + index_rf->own_fields = true; + index = index_rf; + } + + return index; +} + + + + +}; // namespace faiss diff --git a/AutoTune.h b/AutoTune.h new file mode 100644 index 000000000..c7238156d --- /dev/null +++ b/AutoTune.h @@ -0,0 +1,213 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved. +// -*- c++ -*- + +#ifndef FAISS_AUTO_TUNE_H +#define FAISS_AUTO_TUNE_H + +#include + +#include "Index.h" + +namespace faiss { + + +/** + * Evaluation criterion. Returns a performance measure in [0,1], + * higher is better. + */ +struct AutoTuneCriterion { + typedef Index::idx_t idx_t; + idx_t nq; ///< nb of queries this criterion is evaluated on + idx_t nnn; ///< nb of NNs that the query should request + idx_t gt_nnn; ///< nb of GT NNs required to evaluate crterion + + std::vector gt_D; ///< Ground-truth distances (size nq * gt_nnn) + std::vector gt_I; ///< Ground-truth indexes (size nq * gt_nnn) + + AutoTuneCriterion (idx_t nq, idx_t nnn); + + /** Intitializes the gt_D and gt_I vectors. Must be called before evaluating + * + * @param gt_D_in size nq * gt_nnn + * @param gt_I_in size nq * gt_nnn + */ + void set_groundtruth (int gt_nnn, const float *gt_D_in, + const idx_t *gt_I_in); + + /** Evaluate the criterion. + * + * @param D size nq * nnn + * @param I size nq * nnn + * @return the criterion, between 0 and 1. Larger is better. + */ + virtual double evaluate (const float *D, const idx_t *I) const = 0; + + virtual ~AutoTuneCriterion () {} + +}; + +struct OneRecallAtRCriterion: AutoTuneCriterion { + + idx_t R; + + OneRecallAtRCriterion (idx_t nq, idx_t R); + + virtual double evaluate (const float *D, const idx_t *I) const override; + + virtual ~OneRecallAtRCriterion () {} +}; + + +struct IntersectionCriterion: AutoTuneCriterion { + + idx_t R; + + IntersectionCriterion (idx_t nq, idx_t R); + + virtual double evaluate (const float *D, const idx_t *I) const override; + + virtual ~IntersectionCriterion () {} +}; + +/** + * Maintains a list of experimental results. Each operating point is a + * (perf, t, key) triplet, where higher perf and lower t is + * better. The key field is an arbitrary identifier for the operating point + */ + +struct OperatingPoint { + double perf; ///< performance measure (output of a Criterion) + double t; ///< corresponding execution time (ms) + std::string key; ///< key that identifies this op pt + long cno; ///< integer identifer +}; + +struct OperatingPoints { + /// all operating points + std::vector all_pts; + + /// optimal operating points, sorted by perf + std::vector optimal_pts; + + // begins with a single operating point: t=0, perf=0 + OperatingPoints (); + + /// add operating points from other to this, with a prefix to the keys + int merge_with (const OperatingPoints &other, + const std::string & prefix = ""); + + void clear (); + + /// add a performance measure. Return whether it is an optimal point + bool add (double perf, double t, const std::string & key, size_t cno = 0); + + /// get time required to obtain a given performance measure + double t_for_perf (double perf) const; + + /// easy-to-read output + void display (bool only_optimal = true) const; + + /// output to a format easy to digest by gnuplot + void all_to_gnuplot (const char *fname) const; + void optimal_to_gnuplot (const char *fname) const; + +}; + +/// possible values of a parameter, sorted from least to most expensive/accurate +struct ParameterRange { + std::string name; + std::vector values; +}; + +/** Uses a-priori knowledge on the Faiss indexes to extract tunable parameters. + */ +struct ParameterSpace { + /// all tunable parameters + std::vector parameter_ranges; + + // exploration parameters + + /// verbosity during exploration + int verbose; + + /// nb of experiments during optimization (0 = try all combinations) + int n_experiments; + + /// maximum number of queries to submit at a time. + size_t batchsize; + + /// use multithreading over batches (useful to benchmark + /// independent single-searches) + bool thread_over_batches; + + ParameterSpace (); + + /// nb of combinations, = product of values sizes + size_t n_combinations () const; + + /// returns whether combinations c1 >= c2 in the tuple sense + bool combination_ge (size_t c1, size_t c2) const; + + /// get string representation of the combination + std::string combination_name (size_t cno) const; + + /// print a description on stdout + void display () const; + + /// add a new parameter + ParameterRange &add_range(const char * name); + + /// initialize with reasonable parameters for the index + virtual void initialize (const Index * index); + + /// set a combination of parameters on an index + void set_index_parameters (Index *index, size_t cno) const; + + /// set a combination of parameters described by a string + void set_index_parameters (Index *index, const char *param_string) const; + + /// set one of the parameters + virtual void set_index_parameter ( + Index * index, const std::string & name, double val) const; + + /** find an upper bound on the performance and a lower bound on t + * for configuration cno given another operating point op */ + void update_bounds (size_t cno, const OperatingPoint & op, + double *upper_bound_perf, + double *lower_bound_t) const; + + /** explore operating points + * @param index index to run on + * @param xq query vectors (size nq * index.d) + * @param crit selection criterion + * @param ops resutling operating points + */ + void explore (Index *index, + size_t nq, const float *xq, + const AutoTuneCriterion & crit, + OperatingPoints * ops) const; + + virtual ~ParameterSpace () {} +}; + +/** Build and index with the sequence of processing steps described in + * the string. */ +Index *index_factory (int d, const char *description, + MetricType metric = METRIC_L2); + + + +} // namespace faiss + + + +#endif diff --git a/AuxIndexStructures.cpp b/AuxIndexStructures.cpp new file mode 100644 index 000000000..45374e4a1 --- /dev/null +++ b/AuxIndexStructures.cpp @@ -0,0 +1,206 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved +// -*- c++ -*- + +#include "AuxIndexStructures.h" + +#include + +namespace faiss { + + +/*********************************************************************** + * RangeSearchResult + ***********************************************************************/ + +RangeSearchResult::RangeSearchResult (size_t nq): nq (nq) { + lims = new size_t [nq + 1]; + memset (lims, 0, sizeof(*lims) * (nq + 1)); + labels = nullptr; + distances = nullptr; + buffer_size = 1024 * 256; +} + +/// called when lims contains the nb of elements result entries +/// for each query +void RangeSearchResult::do_allocation () { + size_t ofs = 0; + for (int i = 0; i < nq; i++) { + size_t n = lims[i]; + lims [i] = ofs; + ofs += n; + } + lims [nq] = ofs; + labels = new idx_t [ofs]; + distances = new float [ofs]; +} + +RangeSearchResult::~RangeSearchResult () { + delete [] labels; + delete [] distances; + delete [] lims; +} + +/*********************************************************************** + * BufferList + ***********************************************************************/ + + +BufferList::BufferList (size_t buffer_size): + buffer_size (buffer_size) +{ + wp = buffer_size; +} + +BufferList::~BufferList () +{ + for (int i = 0; i < buffers.size(); i++) { + delete [] buffers[i].ids; + delete [] buffers[i].dis; + } +} + + + +void BufferList::append_buffer () +{ + Buffer buf = {new idx_t [buffer_size], new float [buffer_size]}; + buffers.push_back (buf); + wp = 0; +} + +/// copy elemnts ofs:ofs+n-1 seen as linear data in the buffers to +/// tables dest_ids, dest_dis +void BufferList::copy_range (size_t ofs, size_t n, + idx_t * dest_ids, float *dest_dis) +{ + size_t bno = ofs / buffer_size; + ofs -= bno * buffer_size; + while (n > 0) { + size_t ncopy = ofs + n < buffer_size ? n : buffer_size - ofs; + Buffer buf = buffers [bno]; + memcpy (dest_ids, buf.ids + ofs, ncopy * sizeof(*dest_ids)); + memcpy (dest_dis, buf.dis + ofs, ncopy * sizeof(*dest_dis)); + dest_ids += ncopy; + dest_dis += ncopy; + ofs = 0; + bno ++; + n -= ncopy; + } +} + + +/*********************************************************************** + * RangeSearchPartialResult + ***********************************************************************/ + + +RangeSearchPartialResult::RangeSearchPartialResult (RangeSearchResult * res_in): + BufferList(res_in->buffer_size), + res(res_in) +{} + + +/// begin a new result +RangeSearchPartialResult::QueryResult & + RangeSearchPartialResult::new_result (idx_t qno) +{ + QueryResult qres = {qno, 0, this}; + queries.push_back (qres); + return queries.back(); +} + + +void RangeSearchPartialResult::finalize () +{ + set_lims (); +#pragma omp barrier + +#pragma omp single + res->do_allocation (); + +#pragma omp barrier + set_result (); +} + + +/// called by range_search before do_allocation +void RangeSearchPartialResult::set_lims () +{ + for (int i = 0; i < queries.size(); i++) { + QueryResult & qres = queries[i]; + res->lims[qres.qno] = qres.nres; + } +} + +/// called by range_search after do_allocation +void RangeSearchPartialResult::set_result (bool incremental) +{ + size_t ofs = 0; + for (int i = 0; i < queries.size(); i++) { + QueryResult & qres = queries[i]; + + copy_range (ofs, qres.nres, + res->labels + res->lims[qres.qno], + res->distances + res->lims[qres.qno]); + if (incremental) { + res->lims[qres.qno] += qres.nres; + } + ofs += qres.nres; + } +} + + +IDSelectorRange::IDSelectorRange (idx_t imin, idx_t imax): + imin (imin), imax (imax) +{ +} + +bool IDSelectorRange::is_member (idx_t id) const +{ + return id >= imin && id < imax; +} + + + +IDSelectorBatch::IDSelectorBatch (long n, const idx_t *indices) +{ + nbits = 0; + while (n > (1L << nbits)) nbits++; + nbits += 5; + // for n = 1M, nbits = 25 is optimal, see P56659518 + + mask = (1L << nbits) - 1; + bloom.resize (1UL << (nbits - 3), 0); + for (long i = 0; i < n; i++) { + long id = indices[i]; + set.insert(id); + id &= mask; + bloom[id >> 3] |= 1 << (id & 7); + } +} + +bool IDSelectorBatch::is_member (idx_t i) const +{ + long im = i & mask; + if(!(bloom[im>>3] & (1 << (im & 7)))) { + return 0; + } + return set.count(i); +} + + + + + + + +} diff --git a/AuxIndexStructures.h b/AuxIndexStructures.h new file mode 100644 index 000000000..f22ddc386 --- /dev/null +++ b/AuxIndexStructures.h @@ -0,0 +1,189 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved +// -*- c++ -*- +// Auxiliary index structures, that are used in indexes but that can +// be forward-declared + +#ifndef FAISS_AUX_INDEX_STRUCTURES_H +#define FAISS_AUX_INDEX_STRUCTURES_H + +#include + +#if __cplusplus >= 201103L +#include +#endif + +#include + + +#include "Index.h" + +namespace faiss { + +/** The objective is to have a simple result structure while + * minimizing the number of mem copies in the result. The method + * do_allocation can be overloaded to allocate the result tables in + * the matrix type of a srcipting language like Lua or Python. */ +struct RangeSearchResult { + size_t nq; ///< nb of queries + size_t *lims; ///< size (nq + 1) + + typedef Index::idx_t idx_t; + + idx_t *labels; ///< result for query i is labels[lims[i]:lims[i+1]] + float *distances; ///< corresponding distances (not sorted) + + size_t buffer_size; ///< size of the result buffers used + + /// lims must be allocated on input to range_search. + explicit RangeSearchResult (size_t nq); + + /// called when lims contains the nb of elements result entries + /// for each query + virtual void do_allocation (); + + virtual ~RangeSearchResult (); +}; + + +/** Encapsulates a set of ids to remove. */ +struct IDSelector { + typedef Index::idx_t idx_t; + virtual bool is_member (idx_t id) const = 0; + virtual ~IDSelector() {} +}; + + + +/** remove ids between [imni, imax) */ +struct IDSelectorRange: IDSelector { + idx_t imin, imax; + + IDSelectorRange (idx_t imin, idx_t imax); + virtual bool is_member (idx_t id) const override; + virtual ~IDSelectorRange () {} + +}; + + +/** Remove ids from a set. Repetitions of ids in the indices set + * passed to the constructor does not hurt performance. The hash + * function used for the bloom filter and GCC's implementation of + * unordered_set are just the least significant bits of the id. This + * works fine for random ids or ids in sequences but will produce many + * hash collisions if lsb's are always the same */ +struct IDSelectorBatch: IDSelector { + +#if __cplusplus >= 201103L + std::unordered_set set; +#else + std::set set; +#endif + + typedef unsigned char uint8_t; + std::vector bloom; // assumes low bits of id are a good hash value + int nbits; + idx_t mask; + + IDSelectorBatch (long n, const idx_t *indices); + virtual bool is_member (idx_t id) const override; + virtual ~IDSelectorBatch() {} + +}; + + +// Below are structures used only by Index implementations + + + +/** List of temporary buffers used to store results before they are + * copied to the RangeSearchResult object. */ +struct BufferList { + typedef Index::idx_t idx_t; + + // buffer sizes in # entries + size_t buffer_size; + + struct Buffer { + idx_t *ids; + float *dis; + }; + + std::vector buffers; + size_t wp; ///< write pointer in the last buffer. + + explicit BufferList (size_t buffer_size); + + ~BufferList (); + + // create a new buffer + void append_buffer (); + + inline void add (idx_t id, float dis) + { + if (wp == buffer_size) { // need new buffer + append_buffer(); + } + Buffer & buf = buffers.back(); + buf.ids [wp] = id; + buf.dis [wp] = dis; + wp++; + } + + /// copy elemnts ofs:ofs+n-1 seen as linear data in the buffers to + /// tables dest_ids, dest_dis + void copy_range (size_t ofs, size_t n, + idx_t * dest_ids, float *dest_dis); + +}; + + + +/// the entries in the buffers are split per query +struct RangeSearchPartialResult: BufferList { + RangeSearchResult * res; + + explicit RangeSearchPartialResult (RangeSearchResult * res_in); + + /// result structure for a single query + struct QueryResult { + idx_t qno; + size_t nres; + RangeSearchPartialResult * pres; + inline void add (float dis, idx_t id) { + nres++; + pres->add (id, dis); + } + }; + + std::vector queries; + + /// begin a new result + QueryResult & new_result (idx_t qno); + + + void finalize (); + + + /// called by range_search before do_allocation + void set_lims (); + + /// called by range_search after do_allocation + void set_result (bool incremental = false); + +}; + + +}; // namespace faiss + + + +#endif diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..100df164e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,52 @@ +# Contributing to Faiss + +We want to make contributing to this project as easy and transparent as +possible. + +## Our Development Process + +We mainly develop Faiss within Facebook. Sometimes, we will sync the +github version of Faiss with the internal state. + +## Pull Requests + +We welcome pull requests that add significant value to Faiss. + +1. Fork the repo and create your branch from `master`. +2. If you've added code that should be tested, add tests. +3. If you've changed APIs, update the documentation. +4. Ensure the test suite passes. +5. Make sure your code lints. +6. If you haven't already, complete the Contributor License Agreement ("CLA"). + +There is a Facebook internal test suite for Faiss, and we need to run +all changes to Faiss through it. + +## Contributor License Agreement ("CLA") + +In order to accept your pull request, we need you to submit a CLA. You only need +to do this once to work on any of Facebook's open source projects. + +Complete your CLA here: + +## Issues + +We use GitHub issues to track public bugs. Please ensure your description is +clear and has sufficient instructions to be able to reproduce the issue. + +Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe +disclosure of security bugs. In those cases, please go through the process +outlined on that page and do not file a public issue. + +## Coding Style +* 4 spaces for indentation rather than tabs +* 80 character line length +* C++03 for the main Faiss + + +## License +By contributing to Faiss, you agree that your contributions will be licensed +under the LICENSE file in the root directory of this source tree. + + + diff --git a/Clustering.cpp b/Clustering.cpp new file mode 100644 index 000000000..4f59aebf3 --- /dev/null +++ b/Clustering.cpp @@ -0,0 +1,217 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +/* Copyright 2004-present Facebook. All Rights Reserved. + kmeans clustering routines +*/ + +#include "Clustering.h" + + + +#include +#include +#include + +#include "utils.h" +#include "FaissAssert.h" +#include "IndexFlat.h" + +namespace faiss { + +ClusteringParameters::ClusteringParameters (): + niter(25), + nredo(1), + verbose(false), spherical(false), + update_index(false), + min_points_per_centroid(39), + max_points_per_centroid(256), + seed(1234) +{} +// 39 corresponds to 10000 / 256 -> to avoid warnings on PQ tests with randu10k + + +Clustering::Clustering (int d, int k): + d(d), k(k) {} + +Clustering::Clustering (int d, int k, const ClusteringParameters &cp): + ClusteringParameters (cp), d(d), k(k) {} + + + +static double imbalance_factor (int n, int k, long *assign) { + std::vector hist(k, 0); + for (int i = 0; i < n; i++) + hist[assign[i]]++; + + double tot = 0, uf = 0; + + for (int i = 0 ; i < k ; i++) { + tot += hist[i]; + uf += hist[i] * (double) hist[i]; + } + uf = uf * k / (tot * tot); + + return uf; +} + + + + +void Clustering::train (idx_t nx, const float *x_in, Index & index) { + FAISS_ASSERT (nx >= k || + !"need at least as many training points as clusters"); + + double t0 = getmillisecs(); + + // yes it is the user's responsibility, but it may spare us some + // hard-to-debug reports. + for (size_t i = 0; i < nx * d; i++) { + FAISS_ASSERT (finite (x_in[i]) || + !"input contains NaN's or Inf's"); + } + + const float *x = x_in; + + if (nx > k * max_points_per_centroid) { + if (verbose) + printf("Sampling a subset of %ld / %ld for training\n", + k * max_points_per_centroid, nx); + int *perm = new int[nx]; + rand_perm (perm, nx, seed); + nx = k * max_points_per_centroid; + float * x_new = new float [nx * d]; + for (idx_t i = 0; i < nx; i++) + memcpy (x_new + i * d, x + perm[i] * d, sizeof(x_new[0]) * d); + delete [] perm; + x = x_new; + } else if (nx < k * min_points_per_centroid) { + fprintf (stderr, + "WARNING clustering %ld points to %ld centroids: " + "please provide at least %ld training points\n", + nx, k, idx_t(k) * min_points_per_centroid); + } + + if (verbose) + printf("Clustering %d points in %ldD to %ld clusters, " + "redo %d times, %d iterations\n", + int(nx), d, k, nredo, niter); + + + + idx_t * assign = new idx_t[nx]; + float * dis = new float[nx]; + + float best_err = 1e50; + double t_search_tot = 0; + if (verbose) { + printf(" Preprocessing in %5g s\n", + (getmillisecs() - t0)/1000.); + } + t0 = getmillisecs(); + + for (int redo = 0; redo < nredo; redo++) { + + std::vector buf_centroids; + + std::vector &cur_centroids = + nredo == 1 ? centroids : buf_centroids; + + if (verbose && nredo > 1) { + printf("Outer iteration %d / %d\n", redo, nredo); + } + + if (cur_centroids.size() == 0) { + // initialize centroids with random points from the dataset + cur_centroids.resize (d * k); + int *perm = new int[nx]; + rand_perm (perm, nx, seed + 1 + redo * 15486557L); +#pragma omp parallel for + for (int i = 0; i < k ; i++) + memcpy (&cur_centroids[i * d], x + perm[i] * d, + d * sizeof (float)); + delete [] perm; + } else { // assume user provides some meaningful initialization + FAISS_ASSERT (cur_centroids.size() == d * k); + FAISS_ASSERT (nredo == 1 || + !"will redo with same initialization"); + } + + if (spherical) + fvec_renorm_L2 (d, k, cur_centroids.data()); + + if (!index.is_trained) + index.train (k, cur_centroids.data()); + + FAISS_ASSERT (index.ntotal == 0 ); + index.add (k, cur_centroids.data()); + float err = 0; + for (int i = 0; i < niter; i++) { + double t0s = getmillisecs(); + index.search (nx, x, 1, dis, assign); + t_search_tot += getmillisecs() - t0s; + + err = 0; + for (int j = 0; j < nx; j++) + err += dis[j]; + obj.push_back (err); + + int nsplit = km_update_centroids (x, cur_centroids.data(), + assign, d, k, nx); + + if (verbose) { + printf (" Iteration %d (%5g s, search %5g s): " + "objective=%g imbalance=%g nsplit=%d \r", + i, (getmillisecs() - t0) / 1000.0, + t_search_tot / 1000, + err, imbalance_factor (nx, k, assign), + nsplit); + fflush (stdout); + } + + if (spherical) + fvec_renorm_L2 (d, k, cur_centroids.data()); + + index.reset (); + if (update_index) + index.train (k, cur_centroids.data()); + + assert (index.ntotal == 0); + index.add (k, centroids.data()); + } + if (verbose) printf("\n"); + if (nredo > 1) { + if (err < best_err) { + if (verbose) + printf ("Keep new clusters\n"); + centroids = cur_centroids; + best_err = err; + } + } + } + + delete [] assign; + delete [] dis; + if (x_in != x) delete [] x; +} + +float kmeans_clustering (size_t d, size_t n, size_t k, + const float *x, + float *centroids) +{ + Clustering clus (d, k); + clus.verbose = d * n * k > (1L << 30); + // display logs if > 1Gflop per iteration + IndexFlatL2 index (d); + clus.train (n, x, index); + memcpy(centroids, clus.centroids.data(), sizeof(*centroids) * d * k); + return clus.obj.back(); +} + +} // namespace faiss diff --git a/Clustering.h b/Clustering.h new file mode 100644 index 000000000..ad006d6e4 --- /dev/null +++ b/Clustering.h @@ -0,0 +1,99 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved +// -*- c++ -*- + +#ifndef FAISS_CLUSTERING_H +#define FAISS_CLUSTERING_H +#include "Index.h" + +#include + +namespace faiss { + + +/** Class for the clustering parameters. Can be passed to the + * constructor of the Clustering object. + */ +struct ClusteringParameters { + int niter; ///< clustering iterations + int nredo; ///< redo clustering this many times and keep best + + + bool verbose; + bool spherical; ///< do we want normalized centroids? + bool update_index; ///< update index after each iteration? + + int min_points_per_centroid; ///< otherwise you get a warning + int max_points_per_centroid; ///< to limit size of dataset + + int seed; ///< seed for the random number generator + + /// sets reasonable defaults + ClusteringParameters (); +}; + + +/** clustering based on assignment - centroid update iterations + * + * The clustering is based on an Index object that assigns training + * points to the centroids. Therefore, at each iteration the centroids + * are added to the index. + * + * On output, the centoids table is set to the latest version + * of the centroids and they are also added to the index. If the + * centroids table it is not empty on input, it is also used for + * initialization. + * + * To do several clusterings, just call train() several times on + * different training sets, clearing the centroid table in between. + */ +struct Clustering: ClusteringParameters { + typedef Index::idx_t idx_t; + size_t d; ///< dimension of the vectors + size_t k; ///< nb of centroids + + /// centroids (k * d) + std::vector centroids; + + /// objective values (sum of distances reported by index) over + /// iterations + std::vector obj; + + /// the only mandatory parameters are k and d + Clustering (int d, int k); + Clustering (int d, int k, const ClusteringParameters &cp); + + /// Index is used during the assignment stage + virtual void train (idx_t n, const float * x, faiss::Index & index); + + virtual ~Clustering() {} +}; + + +/** simplified interface + * + * @param d dimension of the data + * @param n nb of training vectors + * @param k nb of output centroids + * @param x training set (size n * d) + * @param centroids output centroids (size k * d) + * @return final quantization error + */ +float kmeans_clustering (size_t d, size_t n, size_t k, + const float *x, + float *centroids); + + + +} + + +#endif diff --git a/FaissAssert.h b/FaissAssert.h new file mode 100644 index 000000000..6c9e2170f --- /dev/null +++ b/FaissAssert.h @@ -0,0 +1,26 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved. + +#ifndef FAISS_ASSERT_INCLUDED +#define FAISS_ASSERT_INCLUDED + +#include +#include + +/// Asserts that risk to be triggered by user input +#define FAISS_ASSERT(X) ({if (! (X)) { \ + fprintf (stderr, "Faiss assertion %s failed in %s at %s:%d", \ + #X, __PRETTY_FUNCTION__, __FILE__, __LINE__); \ + abort(); }}) + + + +#endif diff --git a/Heap.cpp b/Heap.cpp new file mode 100644 index 000000000..d4f50da28 --- /dev/null +++ b/Heap.cpp @@ -0,0 +1,133 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +/* Copyright 2004-present Facebook. All Rights Reserved. */ +/* Function for soft heap */ + +#include "Heap.h" + + +namespace faiss { + + + + + + + + + +template +void HeapArray::heapify () +{ +#pragma omp parallel for + for (size_t j = 0; j < nh; j++) + heap_heapify (k, val + j * k, ids + j * k); +} + +template +void HeapArray::reorder () +{ +#pragma omp parallel for + for (size_t j = 0; j < nh; j++) + heap_reorder (k, val + j * k, ids + j * k); +} + +template +void HeapArray::addn (size_t nj, const T *vin, TI j0, + size_t i0, long ni) +{ + if (ni == -1) ni = nh; + assert (i0 >= 0 && i0 + ni <= nh); +#pragma omp parallel for + for (size_t i = i0; i < i0 + ni; i++) { + T * __restrict simi = get_val(i); + TI * __restrict idxi = get_ids (i); + const T *ip_line = vin + (i - i0) * nj; + + for (size_t j = 0; j < nj; j++) { + T ip = ip_line [j]; + if (C::cmp(simi[0], ip)) { + heap_pop (k, simi, idxi); + heap_push (k, simi, idxi, ip, j + j0); + } + } + } +} + +template +void HeapArray::addn_with_ids ( + size_t nj, const T *vin, const TI *id_in, + long id_stride, size_t i0, long ni) +{ + if (id_in == nullptr) { + addn (nj, vin, 0, i0, ni); + return; + } + if (ni == -1) ni = nh; + assert (i0 >= 0 && i0 + ni <= nh); +#pragma omp parallel for + for (size_t i = i0; i < i0 + ni; i++) { + T * __restrict simi = get_val(i); + TI * __restrict idxi = get_ids (i); + const T *ip_line = vin + (i - i0) * nj; + const TI *id_line = id_in + (i - i0) * id_stride; + + for (size_t j = 0; j < nj; j++) { + T ip = ip_line [j]; + if (C::cmp(simi[0], ip)) { + heap_pop (k, simi, idxi); + heap_push (k, simi, idxi, ip, id_line [j]); + } + } + } +} + +template +void HeapArray::per_line_extrema ( + T * out_val, + TI * out_ids) const +{ +#pragma omp parallel for + for (size_t j = 0; j < nh; j++) { + long imin = -1; + typename C::T xval = C::Crev::neutral (); + const typename C::T * x_ = val + j * k; + for (size_t i = 0; i < k; i++) + if (C::cmp (x_[i], xval)) { + xval = x_[i]; + imin = i; + } + if (out_val) + out_val[j] = xval; + + if (out_ids) { + if (ids && imin != -1) + out_ids[j] = ids [j * k + imin]; + else + out_ids[j] = imin; + } + } +} + + + + +// explicit instanciations + +template class HeapArray >; +template class HeapArray >; +template class HeapArray >; +template class HeapArray >; + + + + + +} // END namespace fasis diff --git a/Heap.h b/Heap.h new file mode 100644 index 000000000..601833911 --- /dev/null +++ b/Heap.h @@ -0,0 +1,498 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +/* Copyright 2004-present Facebook. All Rights Reserved. + * + * C++ support for heaps. The set of functions is tailored for + * efficient similarity search. + * + * There is no specific object for a heap, and the functions that + * operate on a signle heap are inlined, because heaps are often + * small. More complex functions are implemented in Heaps.cpp + * + */ + + +#ifndef FAISS_Heap_h +#define FAISS_Heap_h + +#include +#include +#include + +#include +#include + +#include + + + +namespace faiss { + +/******************************************************************* + * C object: uniform handling of min and max heap + *******************************************************************/ + +/** The C object gives the type T of the values in the heap, the type + * of the keys, TI and the comparison that is done: > for the minheap + * and < for the maxheap. The neutral value will always be dropped in + * favor of any other value in the heap. + */ + +template +struct CMax; + +// traits of minheaps = heaps where the minimum value is stored on top +// useful to find the *max* values of an array +template +struct CMin { + typedef T_ T; + typedef TI_ TI; + typedef CMax Crev; + inline static bool cmp (T a, T b) { + return a < b; + } + // value that will be popped first -> must be smaller than all others + // for int types this is not strictly the smallest val (-max - 1) + inline static T neutral () { + return -std::numeric_limits::max(); + } +}; + + +template +struct CMax { + typedef T_ T; + typedef TI_ TI; + typedef CMin Crev; + inline static bool cmp (T a, T b) { + return a > b; + } + inline static T neutral () { + return std::numeric_limits::max(); + } +}; + + +/******************************************************************* + * Basic heap ops: push and pop + *******************************************************************/ + +/** Pops the top element from the heap defined by bh_val[0..k-1] and + * bh_ids[0..k-1]. on output the element at k-1 is undefined. + */ +template inline +void heap_pop (size_t k, typename C::T * bh_val, typename C::TI * bh_ids) +{ + bh_val--; /* Use 1-based indexing for easier node->child translation */ + bh_ids--; + typename C::T val = bh_val[k]; + size_t i = 1, i1, i2; + while (1) { + i1 = i << 1; + i2 = i1 + 1; + if (i1 > k) + break; + if (i2 == k + 1 || C::cmp(bh_val[i1], bh_val[i2])) { + if (C::cmp(val, bh_val[i1])) + break; + bh_val[i] = bh_val[i1]; + bh_ids[i] = bh_ids[i1]; + i = i1; + } + else { + if (C::cmp(val, bh_val[i2])) + break; + bh_val[i] = bh_val[i2]; + bh_ids[i] = bh_ids[i2]; + i = i2; + } + } + bh_val[i] = bh_val[k]; + bh_ids[i] = bh_ids[k]; +} + + + +/** Pushes the element (val, ids) into the heap bh_val[0..k-2] and + * bh_ids[0..k-2]. on output the element at k-1 is defined. + */ +template inline +void heap_push (size_t k, + typename C::T * bh_val, typename C::TI * bh_ids, + typename C::T val, typename C::TI ids) +{ + bh_val--; /* Use 1-based indexing for easier node->child translation */ + bh_ids--; + size_t i = k, i_father; + while (i > 1) { + i_father = i >> 1; + if (!C::cmp (val, bh_val[i_father])) /* the heap structure is ok */ + break; + bh_val[i] = bh_val[i_father]; + bh_ids[i] = bh_ids[i_father]; + i = i_father; + } + bh_val[i] = val; + bh_ids[i] = ids; +} + + + +/* Partial instanciation for heaps with TI = long */ + +template inline +void minheap_pop (size_t k, T * bh_val, long * bh_ids) +{ + heap_pop > (k, bh_val, bh_ids); +} + + +template inline +void minheap_push (size_t k, T * bh_val, long * bh_ids, T val, long ids) +{ + heap_push > (k, bh_val, bh_ids, val, ids); +} + + +template inline +void maxheap_pop (size_t k, T * bh_val, long * bh_ids) +{ + heap_pop > (k, bh_val, bh_ids); +} + + +template inline +void maxheap_push (size_t k, T * bh_val, long * bh_ids, T val, long ids) +{ + heap_push > (k, bh_val, bh_ids, val, ids); +} + + + +/******************************************************************* + * Heap initialization + *******************************************************************/ + +/* Initialization phase for the heap (with inconditionnal pushes). + * Store k0 elements in a heap containing up to k values. Note that + * (bh_val, bh_ids) can be the same as (x, ids) */ +template inline +void heap_heapify ( + size_t k, + typename C::T * bh_val, + typename C::TI * bh_ids, + const typename C::T * x = nullptr, + const typename C::TI * ids = nullptr, + size_t k0 = 0) +{ + if (k0 > 0) assert (x); + + if (ids) { + for (size_t i = 0; i < k0; i++) + heap_push (i+1, bh_val, bh_ids, x[i], ids[i]); + } else { + for (size_t i = 0; i < k0; i++) + heap_push (i+1, bh_val, bh_ids, x[i], i); + } + + for (size_t i = k0; i < k; i++) { + bh_val[i] = C::neutral(); + bh_ids[i] = -1; + } + +} + +template inline +void minheap_heapify ( + size_t k, T * bh_val, + long * bh_ids, + const T * x = nullptr, + const long * ids = nullptr, + size_t k0 = 0) +{ + heap_heapify< CMin > (k, bh_val, bh_ids, x, ids, k0); +} + + +template inline +void maxheap_heapify ( + size_t k, + T * bh_val, + long * bh_ids, + const T * x = nullptr, + const long * ids = nullptr, + size_t k0 = 0) +{ + heap_heapify< CMax > (k, bh_val, bh_ids, x, ids, k0); +} + + + +/******************************************************************* + * Add n elements to the heap + *******************************************************************/ + + +/* Add some elements to the heap */ +template inline +void heap_addn (size_t k, + typename C::T * bh_val, typename C::TI * bh_ids, + const typename C::T * x, + const typename C::TI * ids, + size_t n) +{ + size_t i; + if (ids) + for (i = 0; i < n; i++) { + if (C::cmp (bh_val[0], x[i])) { + heap_pop (k, bh_val, bh_ids); + heap_push (k, bh_val, bh_ids, x[i], ids[i]); + } + } + else + for (i = 0; i < n; i++) { + if (C::cmp (bh_val[0], x[i])) { + heap_pop (k, bh_val, bh_ids); + heap_push (k, bh_val, bh_ids, x[i], i); + } + } +} + + +/* Partial instanciation for heaps with TI = long */ + +template inline +void minheap_addn (size_t k, T * bh_val, long * bh_ids, + const T * x, const long * ids, size_t n) +{ + heap_addn > (k, bh_val, bh_ids, x, ids, n); +} + +template inline +void maxheap_addn (size_t k, T * bh_val, long * bh_ids, + const T * x, const long * ids, size_t n) +{ + heap_addn > (k, bh_val, bh_ids, x, ids, n); +} + + + + + + +/******************************************************************* + * Heap finalization (reorder elements) + *******************************************************************/ + + +/* This function maps a binary heap into an sorted structure. + It returns the number */ +template inline +size_t heap_reorder (size_t k, typename C::T * bh_val, typename C::TI * bh_ids) +{ + size_t i, ii; + + for (i = 0, ii = 0; i < k; i++) { + /* top element should be put at the end of the list */ + typename C::T val = bh_val[0]; + typename C::TI id = bh_ids[0]; + + /* boundary case: we will over-ride this value if not a true element */ + heap_pop (k-i, bh_val, bh_ids); + bh_val[k-ii-1] = val; + bh_ids[k-ii-1] = id; + if (id != -1) ii++; + } + /* Count the number of elements which are effectively returned */ + size_t nel = ii; + + memmove (bh_val, bh_val+k-ii, ii * sizeof(*bh_val)); + memmove (bh_ids, bh_ids+k-ii, ii * sizeof(*bh_ids)); + + for (; ii < k; ii++) { + bh_val[ii] = C::neutral(); + bh_ids[ii] = -1; + } + return nel; +} + +template inline +size_t minheap_reorder (size_t k, T * bh_val, long * bh_ids) +{ + return heap_reorder< CMin > (k, bh_val, bh_ids); +} + +template inline +size_t maxheap_reorder (size_t k, T * bh_val, long * bh_ids) +{ + return heap_reorder< CMax > (k, bh_val, bh_ids); +} + + + + + +/******************************************************************* + * Operations on heap arrays + *******************************************************************/ + +/** a template structure for a set of [min|max]-heaps it is tailored + * so that the actual data of the heaps can just live in compact + * arrays. + */ +template +struct HeapArray { + typedef typename C::TI TI; + typedef typename C::T T; + + size_t nh; ///< number of heaps + size_t k; ///< allocated size per heap + TI * ids; ///< identifiers (size nh * k) + T * val; ///< values (distances or similarities), size nh * k + + /// Return the list of values for a heap + T * get_val (size_t key) { return val + key * k; } + + /// Correspponding identifiers + TI * get_ids (size_t key) { return ids + key * k; } + + /// prepare all the heaps before adding + void heapify (); + + /** add nj elements to heaps i0:i0+ni, with sequential ids + * + * @param nj nb of elements to add to each heap + * @param vin elements to add, size ni * nj + * @param j0 add this to the ids that are added + * @param i0 first heap to update + * @param ni nb of elements to update (-1 = use nh) + */ + void addn (size_t nj, const T *vin, TI j0 = 0, + size_t i0 = 0, long ni = -1); + + /** same as addn + * + * @param id_in ids of the elements to add, size ni * nj + * @param id_stride stride for id_in + */ + void addn_with_ids ( + size_t nj, const T *vin, const TI *id_in = nullptr, + long id_stride = 0, size_t i0 = 0, long ni = -1); + + /// reorder all the heaps + void reorder (); + + /** this is not really a heap function. It just finds the per-line + * extrema of each line of array D + * @param vals_out extreme value of each line (size nh, or NULL) + * @param idx_out index of extreme value (size nh or NULL) + */ + void per_line_extrema (T *vals_out, TI *idx_out) const; + +}; + + +/* Define useful heaps */ +typedef HeapArray > float_minheap_array_t; +typedef HeapArray > int_minheap_array_t; + +typedef HeapArray > float_maxheap_array_t; +typedef HeapArray > int_maxheap_array_t; + +// The heap templates are instanciated explicitly in Heap.cpp + + + + + + + + + + + + + + + + + + + +/********************************************************************* + * Indirect heaps: instead of having + * + * node i = (bh_ids[i], bh_val[i]), + * + * in indirect heaps, + * + * node i = (bh_ids[i], bh_val[bh_ids[i]]), + * + *********************************************************************/ + + +template +inline +void indirect_heap_pop ( + size_t k, + const typename C::T * bh_val, + typename C::TI * bh_ids) +{ + bh_ids--; /* Use 1-based indexing for easier node->child translation */ + typename C::T val = bh_val[bh_ids[k]]; + size_t i = 1; + while (1) { + size_t i1 = i << 1; + size_t i2 = i1 + 1; + if (i1 > k) + break; + typename C::TI id1 = bh_ids[i1], id2 = bh_ids[i2]; + if (i2 == k + 1 || C::cmp(bh_val[id1], bh_val[id2])) { + if (C::cmp(val, bh_val[id1])) + break; + bh_ids[i] = id1; + i = i1; + } else { + if (C::cmp(val, bh_val[id2])) + break; + bh_ids[i] = id2; + i = i2; + } + } + bh_ids[i] = bh_ids[k]; +} + + + +template +inline +void indirect_heap_push (size_t k, + const typename C::T * bh_val, typename C::TI * bh_ids, + typename C::TI id) +{ + bh_ids--; /* Use 1-based indexing for easier node->child translation */ + typename C::T val = bh_val[id]; + size_t i = k; + while (i > 1) { + size_t i_father = i >> 1; + if (!C::cmp (val, bh_val[bh_ids[i_father]])) + break; + bh_ids[i] = bh_ids[i_father]; + i = i_father; + } + bh_ids[i] = id; +} + + + + +} // namespace faiss + +#endif /* FAISS_Heap_h */ diff --git a/INSTALL b/INSTALL new file mode 100644 index 000000000..c3a055db0 --- /dev/null +++ b/INSTALL @@ -0,0 +1,255 @@ + +********************************************************** +** INSTALL file for Faiss (Fair AI Similarity Search). ** +********************************************************** + + +The Faiss installation works in 3 steps, from easiest to most +involved: + +1. compile the C++ core and examples + +2. compile the Python interface + +3. compile GPU part + +Steps 2 and 3 depend on 1, but they are otherwise independent. + +General compilation instructions +================================ + +Faiss has been tested only on x86_64 machines on Linux and Mac OS. + +Faiss is compiled via a Makefile. The system-dependent configuration +of the Makefile is in an include file, makefile.inc. The variables in +makefile.inc must be set by hand. + +Faiss requires a C++ compiler that understands: +- the Intel intrinsics for SSE instructions +- the GCC intrinsic for the popcount instruction +- basic OpenMP + +There are a few models for makefile.inc in the example_makefiles/ +subdirectory. Copy the relevant one for your system and adjust to your +needs. + +Faiss comes as a .a archive, that can be linked with executables or +dynamic libraries (useful for the Python wrapper). + +Step 1: Compiling the C++ Faiss +=============================== + +The CPU version of Faiss is written in C++03, so it should compile +even with relatively old C++ compilers. + +BLAS/Lapack +----------- + +The only variables that need to be configured for the C++ Faiss are +the BLAS/Lapack flags (a linear aglebra software package). It needs a +flag telling whether BLAS/Lapack uses 32 or 64 bit integers and the +linking flags. Faiss uses the Fortran 77 interface of BLAS/Lapack and +thus does not need an include path. + +There are several BLAS implementations, depending on the OS and +machine. To have reasonable performance, the BLAS library should be +multithreaded. See the example makefile.inc's for hints and examples +on how to set the flags. + +To check that the link flags are correct, and verify whether the +implementation uses 32 or 64 bit integers, you can + + make tests/test_blas + +and run + + ./tests/test_blas + +Testing Faiss +------------- + +Once the proper BLAS flags are set, the library should compile +smoothly by running + + make + +A basic usage example is in + + tests/demo_ivfpq_indexing + +it makes a small index, stores it and performs some searches. A normal +runtime is around 20s. With a fast machine and Intel MKL's BLAS it +runs in 2.5s. + +A real-life benchmark +--------------------- + +A bit longer example runs and evaluates Faiss on the SIFT1M +dataset. To run it, please download the ANN_SIFT1M dataset from + +http://corpus-texmex.irisa.fr/ + +and unzip it to the sudirectory sift1M. + +Then compile and run + + make tests/demo_sift1M + tests/demo_sift1M + +This is a demonstration of the high-level auto-tuning API. You can try +setting a different index_key to find the indexing structure that +gives the best performance. + + +Step 2: Compiling the Python interface +====================================== + +The Python interface is provided via SWIG (Simple Wrapper and +Interface Generator) and an additional level of manual wrappers (in faiss.py). + +SWIG generates two wrapper files: a Python file (swigfaiss.py) and a +C++ file that must be compiled to a dynamic library (_swigfaiss.so). + +The C++ compilation to the dynamic library requires to set: + +- SHAREDFLAGS: system-specific flags to generate a dynamic library + +- PYTHONCFLAGS: include flags for Python + +See the example makefile.inc's on how to set the flags. + + +Testing the Python wrapper +-------------------------- + +Often, a successful compile does not mean that the library works, +because missing symbols are detected only at runtime. You should be +able to load the Faiss dynamic library: + + python -c "import faiss" + +In case of failure, it reports the first missing symbol. To see all +missing symbols (on Linux), use + + ldd -r _swigfaiss.so + +Sometimes, problems (eg with BLAS libraries) appear only when actually +calling a BLAS function. A simple way to check this + +python -c "import faiss, numpy +faiss.Kmeans(10, 20).train(numpy.random.rand(1000, 10).astype('float32'))" + + +Real-life test +-------------- + +The following script extends the demo_sift1M test to several types of +indexes: + + python python/demo_auto_tune.py + +It will cycle through a few types of indexes and find optimal +operating points. You can play around with the types of indexes. + + +Step 3: Compiling the GPU implementation +======================================== + +There is a GPU-specific Makefile in the gpu/ directory. It depends on +the same ../makefile.inc for system-specific variables. You need +libfaiss.a from Step 1 for this to work. + +The GPU version is a superset of the CPU version. In addition it +requires: + +- a C++11 compliant compiler (and flags) + +- the cuda compiler and related libraries (Cublas) + +See the example makefile on how to set the flags. + +The nvcc-specific flags to pass to the compiler: + + -gencode arch=compute_35,code="compute_35" + -gencode arch=compute_52,code="compute_52" + -gencode arch=compute_60,code="compute_60" + +Most other flags are related to the C++11 compiler used by nvcc to +complile the actual C++ code. They are normally just transmitted by +nvcc, except some of them that are not recognized and that should be +escaped by prefixing them with -Xcompiler. Also link flags that are +prefixed with -Wl, should be passed with -Xlinker. + +Then compile with + + cd gpu; make + +You may want to add '-j 10' to use 10 threads during compile. + +Testing the GPU implementation +------------------------------ + +Compile the example with + + cd gpu; make test/demo_ivfpq_indexing_gpu + +This produce the GPU code equivalent to the CPU +demo_ivfpq_indexing. It also shows how to translate indexed from/to +the GPU. + +Compiling the Python interface with GPU support +----------------------------------------------- + +Given step 2, adding support of the GPU from Python is quite +straightforward. Run + + cd gpu; make py + +The import is the same for the GPU version and the CPU-only +version. + + python -c "import faiss" + +Faiss tries to load the GPU version first, and in case of failure, +loads the CPU-only version. To investigate more closely the cause of +a failure, you can run: + + python -c "import _swigfaiss_gpu" + +Python example with GPU support +------------------------------- + +The auto-tuning example above also runs on the GPU. Edit + + python/demo_auto_tune.py + +to enable and run it. + +Hot to use Faiss in your own projects +===================================== + +C++ +--- + +The makefile generates a static and a dynamic library + + libfaiss.a + libfaiss.so (or libfaiss.dylib) + +the executable should be linked to one of these. If you use +the static version (.a), add the LDFLAGS found in the Makefile. + +Python +------ + +To import Faiss in your own Python project, you need the files + + faiss.py + swigfaiss.py / swigfaiss_gpu.py + _swigfaiss.so / _swigfaiss_gpu.so + +to be visible in the PYTHONPATH or in the current directory. +Then Faiss can be used in python with + + import faiss + diff --git a/Index.cpp b/Index.cpp new file mode 100644 index 000000000..b3b5c3083 --- /dev/null +++ b/Index.cpp @@ -0,0 +1,73 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved + +#include "IndexFlat.h" +#include "FaissAssert.h" + +namespace faiss { + + +void Index::range_search (idx_t , const float *, float, + RangeSearchResult *) const +{ + FAISS_ASSERT (!"range search not implemented"); +} + +void Index::assign (idx_t n, const float * x, idx_t * labels, idx_t k) +{ + float * distances = new float[n * k]; + search (n, x, k, distances, labels); + delete [] distances; +} + + +void Index::add_with_ids (idx_t n, const float * x, const long *xids) +{ + FAISS_ASSERT (!"add_with_ids not implemented for this type of index"); +} + + +long Index::remove_ids (const IDSelector & sel) +{ + FAISS_ASSERT (!"remove_ids not implemented for this type of index"); + return -1; +} + + +void Index::reconstruct (idx_t, float * ) const { + FAISS_ASSERT (! "Can not compute reconstruct without knowing howto\n"); +} + + +void Index::reconstruct_n (idx_t i0, idx_t ni, float *recons) const { + for (idx_t i = 0; i < ni; i++) { + reconstruct (i0 + i, recons + i * d); + } +} + + + +void Index::compute_residual (const float * x, + float * residual, idx_t key) const { + reconstruct (key, residual); + for (size_t i = 0; i < d; i++) + residual[i] = x[i] - residual[i]; +} + + +void Index::display () const { + printf ("Index: %s -> %ld elements\n", typeid (*this).name(), ntotal); +} + + + + +} diff --git a/Index.h b/Index.h new file mode 100644 index 000000000..eeed03abb --- /dev/null +++ b/Index.h @@ -0,0 +1,199 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved +// -*- c++ -*- + +#ifndef FAISS_INDEX_H +#define FAISS_INDEX_H + + +#include +#include +#include +#include + + +/** + * @namespace faiss + * + * Throughout the library, vectors are provided as float * pointers. + * Most algorithms can be optimized when several vectors are processed + * (added/searched) together in a batch. In this case, they are passed + * in as a matrix. When n vectors of size d are provided as float * x, + * component j of vector i is + * + * x[ i * d + j ] + * + * where 0 <= i < n and 0 <= j < d. In other words, matrices are + * always compact. When specifying the size of the matrix, we call it + * an n*d matrix, which implies a row-major storage. + */ + + +namespace faiss { + + +/// Some algorithms support both an inner product vetsion and a L2 search version. +enum MetricType { + METRIC_INNER_PRODUCT = 0, + METRIC_L2 = 1, +}; + + +/// Forward declarations see AuxIndexStructures.h +struct IDSelector; +struct RangeSearchResult; + +/** Abstract structure for an index + * + * Supports adding vertices and searching them. + * + * Currently only asymmetric queries are supported: + * database-to-database queries are not implemented. + */ +struct Index { + std::string index_typename; + + typedef long idx_t; ///< all indices are this type + + int d; ///< vector dimension + idx_t ntotal; ///< total nb of indexed vectors + bool verbose; ///< verbosity level + + /// set if the Index does not require training, or if training is done already + bool is_trained; + + /// type of metric this index uses for search + MetricType metric_type; + + explicit Index (idx_t d = 0, MetricType metric = METRIC_INNER_PRODUCT): + index_typename ("Undefined Index typename"), + d(d), + ntotal(0), + verbose(false), + is_trained(true), + metric_type (metric) {} + + virtual ~Index () { } + + + /** Perform training on a representative set of vectors + * + * @param n nb of training vectors + * @param x training vecors, size n * d + */ + virtual void train (idx_t n, const float *x) { + // does nothing by default + } + + /** Add n vectors of dimension d to the index. + * + * Vectors are implicitly assigned labels ntotal .. ntotal + n - 1 + * This function slices the input vectors in chuncks smaller than + * blocksize_add and calls add_core. + * @param x input matrix, size n * d + */ + virtual void add (idx_t n, const float *x) = 0; + + /** Same as add, but stores xids instead of sequential ids. + * + * The default implementation fails with an assertion, as it is + * not supported by all indexes. + * + * @param xids if non-null, ids to store for the vectors (size n) + */ + virtual void add_with_ids (idx_t n, const float * x, const long *xids); + + /** query n vectors of dimension d to the index. + * + * return at most k vectors. If there are not enough results for a + * query, the result array is padded with -1s. + * + * @param x input vectors to search, size n * d + * @param labels output labels of the NNs, size n*k + * @param distances output pairwise distances, size n*k + */ + virtual void search (idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const = 0; + + /** query n vectors of dimension d to the index. + * + * return all vectors with distance < radius. Note that many + * indexes do not implement the range_search (only the k-NN search + * is mandatory). + * + * @param x input vectors to search, size n * d + * @param radius search radius + * @param result result table + */ + virtual void range_search (idx_t n, const float *x, float radius, + RangeSearchResult *result) const; + + /** return the indexes of the k vectors closest to the query x. + * + * This function is identical as search but only return labels of neighbors. + * @param x input vectors to search, size n * d + * @param labels output labels of the NNs, size n*k + */ + void assign (idx_t n, const float * x, idx_t * labels, idx_t k = 1); + + /// removes all elements from the database. + virtual void reset() = 0; + + /** removes IDs from the index. Not supported by all indexes + */ + virtual long remove_ids (const IDSelector & sel); + + /** Reconstruct a stored vector (or an approximation if lossy coding) + * + * this function may not be defined for some indexes + * @param key id of the vector to reconstruct + * @param recons reconstucted vector (size d) + */ + virtual void reconstruct (idx_t key, float * recons) const; + + + /** Reconstruct vectors i0 to i0 + ni - 1 + * + * this function may not be defined for some indexes + * @param recons reconstucted vector (size ni * d) + */ + virtual void reconstruct_n (idx_t i0, idx_t ni, float *recons) const; + + + /** Computes a residual vector after indexing encoding. + * + * The residual vector is the difference between a vector and the + * reconstruction that can be decoded from its representation in + * the index. The residual can be used for multiple-stage indexing + * methods, like IndexIVF's methods. + * + * @param x input vector, size d + * @param residual output residual vector, size d + * @param key encoded index, as returned by search and assign + */ + void compute_residual (const float * x, float * residual, idx_t key) const; + + /** Display the actual class name and some more info */ + void display () const; + + /** Return the typeName of the index (which includes main parameters */ + virtual std::string get_typename () const { + return index_typename; } + + virtual void set_typename () = 0 ; + + +}; + +} + + +#endif diff --git a/IndexFlat.cpp b/IndexFlat.cpp new file mode 100644 index 000000000..8b05abfac --- /dev/null +++ b/IndexFlat.cpp @@ -0,0 +1,399 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved + +#include "IndexFlat.h" + +#include +#include "utils.h" +#include "Heap.h" + +#include "FaissAssert.h" + +namespace faiss { + +IndexFlat::IndexFlat (idx_t d, MetricType metric): + Index(d, metric) +{ + set_typename(); +} + + +void IndexFlat::set_typename() +{ + std::stringstream s; + if (metric_type == METRIC_INNER_PRODUCT) + s << "IP"; + else if (metric_type == METRIC_L2) + s << "L2"; + else s << "??"; + index_typename = s.str(); +} + + +void IndexFlat::add (idx_t n, const float *x) { + for (idx_t i = 0; i < n * d; i++) + xb.push_back (x[i]); + ntotal += n; +} + + +void IndexFlat::reset() { + xb.clear(); + ntotal = 0; +} + + +void IndexFlat::search (idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const +{ + // we see the distances and labels as heaps + + if (metric_type == METRIC_INNER_PRODUCT) { + float_minheap_array_t res = { + size_t(n), size_t(k), labels, distances}; + knn_inner_product (x, xb.data(), d, n, ntotal, &res); + } else if (metric_type == METRIC_L2) { + float_maxheap_array_t res = { + size_t(n), size_t(k), labels, distances}; + knn_L2sqr (x, xb.data(), d, n, ntotal, &res); + } +} + +void IndexFlat::range_search (idx_t n, const float *x, float radius, + RangeSearchResult *result) const +{ + switch (metric_type) { + case METRIC_INNER_PRODUCT: + range_search_inner_product (x, xb.data(), d, n, ntotal, + radius, result); + break; + case METRIC_L2: + range_search_L2sqr (x, xb.data(), d, n, ntotal, radius, result); + break; + } +} + + +void IndexFlat::compute_distance_subset ( + idx_t n, + const float *x, + idx_t k, + float *distances, + const idx_t *labels) const +{ + switch (metric_type) { + case METRIC_INNER_PRODUCT: + fvec_inner_products_by_idx ( + distances, + x, xb.data(), labels, d, n, k); + break; + case METRIC_L2: + fvec_L2sqr_by_idx ( + distances, + x, xb.data(), labels, d, n, k); + break; + } + +} + + + +void IndexFlat::reconstruct (idx_t key, float * recons) const +{ + memcpy (recons, &(xb[key * d]), sizeof(*recons) * d); +} + +/*************************************************** + * IndexFlatL2BaseShift + ***************************************************/ + +IndexFlatL2BaseShift::IndexFlatL2BaseShift (idx_t d, size_t nshift, const float *shift): + IndexFlatL2 (d), shift (nshift) +{ + memcpy (this->shift.data(), shift, sizeof(float) * nshift); +} + +void IndexFlatL2BaseShift::search ( + idx_t n, + const float *x, + idx_t k, + float *distances, + idx_t *labels) const +{ + FAISS_ASSERT(shift.size() == ntotal); + + float_maxheap_array_t res = { + size_t(n), size_t(k), labels, distances}; + knn_L2sqr_base_shift (x, xb.data(), d, n, ntotal, &res, shift.data()); +} + + + +/*************************************************** + * IndexRefineFlat + ***************************************************/ + +IndexRefineFlat::IndexRefineFlat (Index *base_index): + Index (base_index->d, base_index->metric_type), + refine_index (base_index->d, base_index->metric_type), + base_index (base_index), own_fields (false), + k_factor (1) +{ + is_trained = base_index->is_trained; + assert (base_index->ntotal == 0 || + !"base_index should be empty in the beginning"); + set_typename (); +} + +IndexRefineFlat::IndexRefineFlat () { + base_index = nullptr; + own_fields = false; + k_factor = 1; +} + +void IndexRefineFlat::set_typename () +{ + std::stringstream s; + s << "Refine" << '[' << base_index->get_typename() + << ',' << refine_index.get_typename() << ']'; + index_typename = s.str(); +} + + +void IndexRefineFlat::train (idx_t n, const float *x) +{ + base_index->train (n, x); + is_trained = true; +} + +void IndexRefineFlat::add (idx_t n, const float *x) { + FAISS_ASSERT (is_trained); + base_index->add (n, x); + refine_index.add (n, x); + ntotal = refine_index.ntotal; +} + +void IndexRefineFlat::reset () +{ + base_index->reset (); + refine_index.reset (); + ntotal = 0; +} + +namespace { +typedef faiss::Index::idx_t idx_t; + +template +static void reorder_2_heaps ( + idx_t n, + idx_t k, idx_t *labels, float *distances, + idx_t k_base, const idx_t *base_labels, const float *base_distances) +{ +#pragma omp parallel for + for (idx_t i = 0; i < n; i++) { + idx_t *idxo = labels + i * k; + float *diso = distances + i * k; + const idx_t *idxi = base_labels + i * k_base; + const float *disi = base_distances + i * k_base; + + heap_heapify (k, diso, idxo, disi, idxi, k); + if (k_base != k) { // add remaining elements + heap_addn (k, diso, idxo, disi + k, idxi + k, k_base - k); + } + heap_reorder (k, diso, idxo); + } +} + + +} + + +void IndexRefineFlat::search ( + idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const +{ + FAISS_ASSERT (is_trained); + idx_t k_base = idx_t (k * k_factor); + idx_t * base_labels = labels; + float * base_distances = distances; + + if (k != k_base) { + base_labels = new idx_t [n * k_base]; + base_distances = new float [n * k_base]; + } + + base_index->search (n, x, k_base, base_distances, base_labels); + + for (int i = 0; i < n * k_base; i++) + assert (base_labels[i] >= -1 && + base_labels[i] < ntotal); + + // compute refined distances + refine_index.compute_distance_subset ( + n, x, k_base, base_distances, base_labels); + + // sort and store result + if (metric_type == METRIC_L2) { + typedef CMax C; + reorder_2_heaps ( + n, k, labels, distances, + k_base, base_labels, base_distances); + + } else if (metric_type == METRIC_INNER_PRODUCT) { + typedef CMin C; + reorder_2_heaps ( + n, k, labels, distances, + k_base, base_labels, base_distances); + } + + if (k != k_base) { + delete [] base_labels; + delete [] base_distances; + } +} + + + +IndexRefineFlat::~IndexRefineFlat () +{ + if (own_fields) delete base_index; +} + +/*************************************************** + * IndexFlat1D + ***************************************************/ + + +IndexFlat1D::IndexFlat1D (bool continuous_update): + IndexFlatL2 (1), + continuous_update (continuous_update) +{ +} + +/// if not continuous_update, call this between the last add and +/// the first search +void IndexFlat1D::update_permutation () +{ + perm.resize (ntotal); + if (ntotal < 1000000) { + fvec_argsort (ntotal, xb.data(), (size_t*)perm.data()); + } else { + fvec_argsort_parallel (ntotal, xb.data(), (size_t*)perm.data()); + } +} + +void IndexFlat1D::add (idx_t n, const float *x) +{ + IndexFlatL2::add (n, x); + if (continuous_update) + update_permutation(); +} + +void IndexFlat1D::reset() +{ + IndexFlatL2::reset(); + perm.clear(); +} + +void IndexFlat1D::search ( + idx_t n, + const float *x, + idx_t k, + float *distances, + idx_t *labels) const +{ + FAISS_ASSERT (perm.size() == ntotal || + !"Call update_permutation before search"); + +#pragma omp parallel for + for (idx_t i = 0; i < n; i++) { + + float q = x[i]; // query + float *D = distances + i * k; + idx_t *I = labels + i * k; + + // binary search + idx_t i0 = 0, i1 = ntotal; + idx_t wp = 0; + + if (xb[perm[i0]] > q) { + i1 = 0; + goto finish_right; + } + + if (xb[perm[i1 - 1]] <= q) { + i0 = i1 - 1; + goto finish_left; + } + + while (i0 + 1 < i1) { + idx_t imed = (i0 + i1) / 2; + if (xb[perm[imed]] <= q) i0 = imed; + else i1 = imed; + } + + // query is between xb[perm[i0]] and xb[perm[i1]] + // expand to nearest neighs + + while (wp < k) { + float xleft = xb[perm[i0]]; + float xright = xb[perm[i1]]; + + if (q - xleft < xright - q) { + D[wp] = q - xleft; + I[wp] = perm[i0]; + i0--; wp++; + if (i0 < 0) { goto finish_right; } + } else { + D[wp] = xright - q; + I[wp] = perm[i1]; + i1++; wp++; + if (i1 >= ntotal) { goto finish_left; } + } + } + goto done; + + finish_right: + // grow to the right from i1 + while (wp < k) { + if (i1 < ntotal) { + D[wp] = xb[perm[i1]] - q; + I[wp] = perm[i1]; + i1++; + } else { + D[wp] = 1.0 / 0.0; + I[wp] = -1; + } + wp++; + } + goto done; + + finish_left: + // grow to the left from i0 + while (wp < k) { + if (i0 >= 0) { + D[wp] = q - xb[perm[i0]]; + I[wp] = perm[i0]; + i0--; + } else { + D[wp] = 1.0 / 0.0; + I[wp] = -1; + } + wp++; + } + done: ; + } + +} + + + +} // namespace faiss diff --git a/IndexFlat.h b/IndexFlat.h new file mode 100644 index 000000000..a54515520 --- /dev/null +++ b/IndexFlat.h @@ -0,0 +1,171 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved +// -*- c++ -*- + +#ifndef INDEX_FLAT_H +#define INDEX_FLAT_H + +#include + +#include "Index.h" + + +namespace faiss { + +/** Index that stores the full vectors and performs exhaustive search */ +struct IndexFlat: Index { + /// database vectors, size ntotal * d + std::vector xb; + + explicit IndexFlat (idx_t d, MetricType metric = METRIC_INNER_PRODUCT); + + virtual void set_typename() override; + + virtual void add (idx_t n, const float *x) override; + + virtual void reset() override; + + virtual void search ( + idx_t n, + const float *x, + idx_t k, + float *distances, + idx_t *labels) const override; + + + + virtual void range_search ( + idx_t n, + const float *x, + float radius, + RangeSearchResult *result) const override; + + virtual void reconstruct (idx_t key, float * recons) + const override; + + + /** compute distance with a subset of vectors + * + * @param x query vectors, size n * d + * @param labels indices of the vectors that should be compared + * for each query vector, size n * k + * @param distances + * corresponding output distances, size n * k + */ + void compute_distance_subset ( + idx_t n, + const float *x, + idx_t k, + float *distances, + const idx_t *labels) const; + + IndexFlat () {} +}; + + + +struct IndexFlatIP:IndexFlat { + explicit IndexFlatIP (idx_t d): IndexFlat (d, METRIC_INNER_PRODUCT) {} + IndexFlatIP () {} +}; + + +struct IndexFlatL2:IndexFlat { + explicit IndexFlatL2 (idx_t d): IndexFlat (d, METRIC_L2) {} + IndexFlatL2 () {} +}; + + +// same as an IndexFlatL2 but a value is subtracted from each distance +struct IndexFlatL2BaseShift: IndexFlatL2 { + std::vector shift; + + IndexFlatL2BaseShift (idx_t d, size_t nshift, const float *shift); + + virtual void search ( + idx_t n, + const float *x, + idx_t k, + float *distances, + idx_t *labels) const override; +}; + + +/** Index that queries in a base_index (a fast one) and refines the + * results with an exact search, hopefully improving the results. + */ +struct IndexRefineFlat: Index { + + /// storage for full vectors + IndexFlat refine_index; + + /// faster index to pre-select the vectors that should be filtered + Index *base_index; + bool own_fields; ///< should the base index be deallocated? + + /// factor between k requested in search and the k requested from + /// the base_index (should be >= 1) + float k_factor; + + explicit IndexRefineFlat (Index *base_index); + + IndexRefineFlat (); + + virtual void train (idx_t n, const float *x) override; + + virtual void add (idx_t n, const float *x) override; + + virtual void reset() override; + + virtual void search ( + idx_t n, + const float *x, + idx_t k, + float *distances, + idx_t *labels) const override; + + virtual void set_typename () override; + + virtual ~IndexRefineFlat (); +}; + + +/// optimized version for 1D "vectors" +struct IndexFlat1D:IndexFlatL2 { + bool continuous_update; ///< is the permutation updated continuously? + + std::vector perm; ///< sorted database indices + + explicit IndexFlat1D (bool continuous_update=true); + + /// if not continuous_update, call this between the last add and + /// the first search + void update_permutation (); + + virtual void add (idx_t n, const float *x) override; + + virtual void reset() override; + + /// Warn: the distances returned are L1 not L2 + virtual void search ( + idx_t n, + const float *x, + idx_t k, + float *distances, + idx_t *labels) const override; + + +}; + + +} + +#endif diff --git a/IndexIVF.cpp b/IndexIVF.cpp new file mode 100644 index 000000000..1c14d93e4 --- /dev/null +++ b/IndexIVF.cpp @@ -0,0 +1,517 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +/* Copyright 2004-present Facebook. All Rights Reserved. + Inverted list structure. +*/ + +#include "IndexIVF.h" + +#include + +#include "utils.h" +#include "hamming.h" + +#include "FaissAssert.h" +#include "IndexFlat.h" +#include "AuxIndexStructures.h" + +namespace faiss { + +/***************************************** + * IndexIVF implementation + ******************************************/ + + +IndexIVF::IndexIVF (Index * quantizer, size_t d, size_t nlist, + MetricType metric): + Index (d, metric), + nlist (nlist), + nprobe (1), + quantizer (quantizer), + quantizer_trains_alone (false), + own_fields (false), + ids (nlist), + maintain_direct_map (false) +{ + FAISS_ASSERT (d == quantizer->d); + is_trained = quantizer->is_trained && (quantizer->ntotal == nlist); + // Spherical by default if the metric is inner_product + if (metric_type == METRIC_INNER_PRODUCT) { + cp.spherical = true; + } + // here we set a low # iterations because this is typically used + // for large clusterings (nb this is not used for the MultiIndex, + // for which quantizer_trains_alone = true) + cp.niter = 10; + cp.verbose = verbose; + +} + +IndexIVF::IndexIVF (): + nlist (0), nprobe (1), quantizer (nullptr), + quantizer_trains_alone (false), own_fields (false), + maintain_direct_map (false) +{} + + +void IndexIVF::add (idx_t n, const float * x) +{ + add_with_ids (n, x, nullptr); +} + +void IndexIVF::make_direct_map () +{ + if (maintain_direct_map) return; + + direct_map.resize (ntotal, -1); + for (size_t key = 0; key < nlist; key++) { + const std::vector & idlist = ids[key]; + + for (long ofs = 0; ofs < idlist.size(); ofs++) { + direct_map [idlist [ofs]] = + key << 32 | ofs; + } + } + + maintain_direct_map = true; +} + + +void IndexIVF::reset () +{ + ntotal = 0; + direct_map.clear(); + for (size_t i = 0; i < ids.size(); i++) + ids[i].clear(); +} + + +void IndexIVF::train (idx_t n, const float *x) +{ + if (quantizer->is_trained && (quantizer->ntotal == nlist)) { + if (verbose) + printf ("IVF quantizer does not need training.\n"); + } else if (quantizer_trains_alone) { + if (verbose) + printf ("IVF quantizer trains alone...\n"); + quantizer->train (n, x); + FAISS_ASSERT (quantizer->ntotal == nlist || + !"nlist not consistent with quantizer size"); + } else { + if (verbose) + printf ("Training IVF quantizer on %ld vectors in %dD\n", + n, d); + + Clustering clus (d, nlist, cp); + + quantizer->reset(); + clus.train (n, x, *quantizer); + quantizer->is_trained = true; + } + if (verbose) + printf ("Training IVF residual\n"); + + train_residual (n, x); + is_trained = true; +} + +void IndexIVF::train_residual (idx_t n, const float *x) +{ + if (verbose) + printf ("IndexIVF: no residual training\n"); + // does nothing by default +} + + + +double IndexIVF::imbalance_factor () const +{ + std::vector hist (nlist); + for (int i = 0; i < nlist; i++) { + hist[i] = ids[i].size(); + } + return faiss::imbalance_factor (nlist, hist.data()); +} + +void IndexIVF::print_stats () const +{ + std::vector sizes(40); + for (int i = 0; i < nlist; i++) { + for (int j = 0; j < sizes.size(); j++) { + if ((ids[i].size() >> j) == 0) { + sizes[j]++; + break; + } + } + } + for (int i = 0; i < sizes.size(); i++) { + if (sizes[i]) { + printf ("list size in < %d: %d instances\n", + 1 << i, sizes[i]); + } + } + +} + +void IndexIVF::merge_from (IndexIVF &other, idx_t add_id) +{ + // minimal sanity checks + FAISS_ASSERT (other.d == d); + FAISS_ASSERT (other.nlist == nlist); + FAISS_ASSERT ((!maintain_direct_map && !other.maintain_direct_map) || + !"direct map copy not implemented"); + FAISS_ASSERT (typeid (*this) == typeid (other) || + !"can only merge indexes of the same type"); + for (long i = 0; i < nlist; i++) { + std::vector & src = other.ids[i]; + std::vector & dest = ids[i]; + for (long j = 0; j < src.size(); j++) + dest.push_back (src[j] + add_id); + src.clear(); + } + merge_from_residuals (other); + ntotal += other.ntotal; + other.ntotal = 0; +} + + + + +IndexIVF::~IndexIVF() +{ + if (own_fields) delete quantizer; +} + + + +/***************************************** + * IndexIVFFlat implementation + ******************************************/ + +IndexIVFFlat::IndexIVFFlat (Index * quantizer, + size_t d, size_t nlist, MetricType metric): + IndexIVF (quantizer, d, nlist, metric) +{ + vecs.resize (nlist); + set_typename(); +} + + +void IndexIVFFlat::set_typename () +{ + std::stringstream s; + if (metric_type == METRIC_INNER_PRODUCT) + s << "IvfIP"; + else if (metric_type == METRIC_L2) + s << "IvfL2"; + else s << "??"; + s << "[" << nlist << ":" << quantizer->index_typename << "]"; + index_typename = s.str(); +} + + + + + + +void IndexIVFFlat::add_with_ids (idx_t n, const float * x, const long *xids) +{ + add_core (n, x, xids, nullptr); +} + +void IndexIVFFlat::add_core (idx_t n, const float * x, const long *xids, + const long *precomputed_idx) + +{ + FAISS_ASSERT (is_trained); + const long * idx; + + if (precomputed_idx) { + idx = precomputed_idx; + } else { + long * idx0 = new long [n]; + quantizer->assign (n, x, idx0); + idx = idx0; + } + long n_add = 0; + for (size_t i = 0; i < n; i++) { + long id = xids ? xids[i] : ntotal + i; + long list_no = idx [i]; + if (list_no < 0) + continue; + FAISS_ASSERT (list_no < nlist); + + ids[list_no].push_back (id); + const float *xi = x + i * d; + /* store the vectors */ + for (size_t j = 0 ; j < d ; j++) + vecs[list_no].push_back (xi [j]); + + if (maintain_direct_map) + direct_map.push_back (list_no << 32 | (ids[list_no].size() - 1)); + n_add++; + } + if (verbose) { + printf("IndexIVFFlat::add_core: added %ld / %ld vectors\n", + n_add, n); + } + if (!precomputed_idx) + delete [] idx; + ntotal += n_add; +} + + + + +void IndexIVFFlat::search_knn_inner_product ( + size_t nx, + const float * x, + const long * __restrict keys, + float_minheap_array_t * res) const +{ + + const size_t k = res->k; + +#pragma omp parallel for + for (size_t i = 0; i < nx; i++) { + const float * xi = x + i * d; + const long * keysi = keys + i * nprobe; + float * __restrict simi = res->get_val (i); + long * __restrict idxi = res->get_ids (i); + minheap_heapify (k, simi, idxi); + + for (size_t ik = 0; ik < nprobe; ik++) { + long key = keysi[ik]; /* select the list */ + if (key < 0) { + // not enough centroids for multiprobe + continue; + } + if (key >= (long) nlist) { + fprintf (stderr, "Invalid key=%ld at ik=%ld nlist=%ld\n", + key, ik, nlist); + throw; + } + + const size_t list_size = ids[key].size(); + const float * list_vecs = vecs[key].data(); + + for (size_t j = 0; j < list_size; j++) { + const float * yj = list_vecs + d * j; + float ip = fvec_inner_product (xi, yj, d); + if (ip > simi[0]) { + minheap_pop (k, simi, idxi); + minheap_push (k, simi, idxi, ip, ids[key][j]); + } + } + } + minheap_reorder (k, simi, idxi); + } +} + + +void IndexIVFFlat::search_knn_L2sqr ( + size_t nx, + const float * x, + const long * __restrict keys, + float_maxheap_array_t * res) const +{ + const size_t k = res->k; + +#pragma omp parallel for + for (size_t i = 0; i < nx; i++) { + const float * xi = x + i * d; + const long * keysi = keys + i * nprobe; + float * __restrict disi = res->get_val (i); + long * __restrict idxi = res->get_ids (i); + maxheap_heapify (k, disi, idxi); + + for (size_t ik = 0; ik < nprobe; ik++) { + long key = keysi[ik]; /* select the list */ + if (key < 0) { + // not enough centroids for multiprobe + continue; + } + if (key >= (long) nlist) { + fprintf (stderr, "Invalid key=%ld at ik=%ld nlist=%ld\n", + key, ik, nlist); + throw; + } + + const size_t list_size = ids[key].size(); + const float * list_vecs = vecs[key].data(); + + for (size_t j = 0; j < list_size; j++) { + const float * yj = list_vecs + d * j; + float disij = fvec_L2sqr (xi, yj, d); + if (disij < disi[0]) { + maxheap_pop (k, disi, idxi); + maxheap_push (k, disi, idxi, disij, ids[key][j]); + } + } + } + maxheap_reorder (k, disi, idxi); + } +} + + +void IndexIVFFlat::search (idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const +{ + idx_t * idx = new idx_t [n * nprobe]; + quantizer->assign (n, x, idx, nprobe); + + if (metric_type == METRIC_INNER_PRODUCT) { + float_minheap_array_t res = { + size_t(n), size_t(k), labels, distances}; + search_knn_inner_product (n, x, idx, &res); + + } else if (metric_type == METRIC_L2) { + float_maxheap_array_t res = { + size_t(n), size_t(k), labels, distances}; + search_knn_L2sqr (n, x, idx, &res); + } + + delete [] idx; +} + + +void IndexIVFFlat::range_search (idx_t nx, const float *x, float radius, + RangeSearchResult *result) const +{ + idx_t * keys = new idx_t [nx * nprobe]; + quantizer->assign (nx, x, keys, nprobe); + + assert (metric_type == METRIC_L2 || !"Only L2 implemented"); +#pragma omp parallel + { + RangeSearchPartialResult pres(result); + + for (size_t i = 0; i < nx; i++) { + const float * xi = x + i * d; + const long * keysi = keys + i * nprobe; + + RangeSearchPartialResult::QueryResult & qres = + pres.new_result (i); + + for (size_t ik = 0; ik < nprobe; ik++) { + long key = keysi[ik]; /* select the list */ + if (key < 0 || key >= (long) nlist) { + fprintf (stderr, "Invalid key=%ld at ik=%ld nlist=%ld\n", + key, ik, nlist); + throw; + } + + const size_t list_size = ids[key].size(); + const float * list_vecs = vecs[key].data(); + + for (size_t j = 0; j < list_size; j++) { + const float * yj = list_vecs + d * j; + float disij = fvec_L2sqr (xi, yj, d); + if (disij < radius) { + qres.add (disij, ids[key][j]); + } + } + } + } + + pres.finalize (); + } + delete[] keys; +} + +void IndexIVFFlat::merge_from_residuals (IndexIVF &other_in) +{ + IndexIVFFlat &other = dynamic_cast (other_in); + for (int i = 0; i < nlist; i++) { + std::vector & src = other.vecs[i]; + std::vector & dest = vecs[i]; + for (int j = 0; j < src.size(); j++) + dest.push_back (src[j]); + src.clear(); + } +} + +void IndexIVFFlat::copy_subset_to (IndexIVFFlat & other, int subset_type, + long a1, long a2) const +{ + FAISS_ASSERT (nlist == other.nlist); + FAISS_ASSERT (!other.maintain_direct_map); + + for (long list_no = 0; list_no < nlist; list_no++) { + const std::vector & ids_in = ids[list_no]; + std::vector & ids_out = other.ids[list_no]; + const std::vector & vecs_in = vecs[list_no]; + std::vector & vecs_out = other.vecs[list_no]; + + for (long i = 0; i < ids_in.size(); i++) { + idx_t id = ids_in[i]; + if (subset_type == 0 && a1 <= id && id < a2) { + ids_out.push_back (id); + vecs_out.insert (vecs_out.end(), + vecs_in.begin() + i * d, + vecs_in.begin() + (i + 1) * d); + other.ntotal++; + } + } + } +} + + + +void IndexIVFFlat::reset() +{ + IndexIVF::reset(); + for (size_t key = 0; key < nlist; key++) { + vecs[key].clear(); + } +} + +long IndexIVFFlat::remove_ids (const IDSelector & sel) +{ + FAISS_ASSERT (!maintain_direct_map || + !"direct map remove not implemented"); + long nremove = 0; +#pragma omp parallel for reduction(+: nremove) + for (long i = 0; i < nlist; i++) { + std::vector & idsi = ids[i]; + float *vecsi = vecs[i].data(); + + long l = idsi.size(), j = 0; + while (j < l) { + if (sel.is_member (idsi[j])) { + l--; + idsi [j] = idsi [l]; + memmove (vecsi + j * d, + vecsi + l * d, d * sizeof (float)); + } else { + j++; + } + } + if (l < idsi.size()) { + nremove += idsi.size() - l; + idsi.resize (l); + vecs[i].resize (l * d); + } + } + ntotal -= nremove; + return nremove; +} + + +void IndexIVFFlat::reconstruct (idx_t key, float * recons) const +{ + assert (direct_map.size() == ntotal); + int list_no = direct_map[key] >> 32; + int ofs = direct_map[key] & 0xffffffff; + memcpy (recons, &vecs[list_no][ofs * d], d * sizeof(recons[0])); +} + + +} // namespace faiss diff --git a/IndexIVF.h b/IndexIVF.h new file mode 100644 index 000000000..6ace5bfbc --- /dev/null +++ b/IndexIVF.h @@ -0,0 +1,187 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved. +// -*- c++ -*- + +#ifndef FAISS_INDEX_IVF_H +#define FAISS_INDEX_IVF_H + + +#include + + +#include "Index.h" +#include "Clustering.h" +#include "Heap.h" + + +namespace faiss { + + + +/** Index based on a inverted file (IVF) + * + * In the inverted file, the quantizer (an Index instance) provides a + * quantization index for each vector to be added. The quantization + * index maps to a list (aka inverted list or posting list), where the + * id of the vector is then stored. + * + * At search time, the vector to be searched is also quantized, and + * only the list corresponding to the quantization index is + * searched. This speeds up the search by making it + * non-exhaustive. This can be relaxed using multi-probe search: a few + * (nprobe) quantization indices are selected and several inverted + * lists are visited. + * + * Sub-classes implement a post-filtering of the index that refines + * the distance estimation from the query to databse vectors. + */ +struct IndexIVF: Index { + size_t nlist; ///< number of possible key values + size_t nprobe; ///< number of probes at query time + + Index * quantizer; ///< quantizer that maps vectors to inverted lists + bool quantizer_trains_alone; ///< just pass over the trainset to quantizer + bool own_fields; ///< whether object owns the quantizer + + ClusteringParameters cp; ///< to override default clustering params + + std::vector < std::vector > ids; ///< Inverted lists for indexes + + /// map for direct access to the elements. Enables reconstruct(). + bool maintain_direct_map; + std::vector direct_map; + + /** The Inverted file takes a quantizer (an Index) on input, + * which implements the function mapping a vector to a list + * identifier. The pointer is borrowed: the quantizer should not + * be deleted while the IndexIVF is in use. + */ + IndexIVF (Index * quantizer, size_t d, size_t nlist, + MetricType metric = METRIC_INNER_PRODUCT); + + virtual void reset () override; + + /// Trains the quantizer and calls train_residual to train sub-quantizers + virtual void train (idx_t n, const float *x) override; + + /// Quantizes x and calls add_with_key + virtual void add (idx_t n, const float *x) override; + + + + /// Sub-classes that encode the residuals can train their encoders here + /// does nothing by default + virtual void train_residual (idx_t n, const float *x); + + /** moves the entries from another dataset to self. On output, + * other is empty. add_id is added to all moved ids (for + * sequential ids, this would be this->ntotal */ + virtual void merge_from (IndexIVF &other, idx_t add_id); + + /** implemented by sub-classes */ + virtual void merge_from_residuals (IndexIVF &other) = 0; + + virtual ~IndexIVF(); + + /// intialize a direct map + void make_direct_map (); + + /// 1= perfectly balanced, >1: imbalanced + double imbalance_factor () const; + + /// display some stats about the inverted lists + void print_stats () const; + + IndexIVF (); +}; + + + + +/** Inverted file with stored vectors. Here the inverted file + * pre-selects the vectors to be searched, but they are not otherwise + * encoded. + */ +struct IndexIVFFlat: IndexIVF { + /** Inverted list of original vectors. Each list is a nl * d + * matrix, where nl is the nb of vectors stored in the list. */ + std::vector < std::vector > vecs; + + IndexIVFFlat ( + Index * quantizer, size_t d, size_t nlist_, + MetricType = METRIC_INNER_PRODUCT); + + /** Return the typeName of the index (which includes main parameters */ + virtual void set_typename () override; + + + /// same as add_with_ids, with precomputed coarse quantizer + void add_core (idx_t n, const float * x, const long *xids, + const long *precomputed_idx); + + + /// implemented for all IndexIVF* classes + virtual void add_with_ids (idx_t n, const float * x, const long *xids) + override; + + virtual void search ( + idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const override; + + virtual void range_search ( + idx_t n, const float *x, float radius, + RangeSearchResult *result) const override; + + /** copy a subset of the entries index to the other index + * + * if subset_type == 0: copies ids in [a1, a2) + * if subset_type == 1: copies ids if id % a1 == a2 + */ + void copy_subset_to (IndexIVFFlat & other, int subset_type, + long a1, long a2) const; + + + virtual void reset() override; + + virtual long remove_ids (const IDSelector & sel) override; + + /// Implementation of the search for the inner product metric + void search_knn_inner_product ( + size_t nx, const float * x, + const long * keys, + float_minheap_array_t * res) const; + + /// Implementation of the search for the L2 metric + void search_knn_L2sqr ( + size_t nx, const float * x, + const long * keys, + float_maxheap_array_t * res) const; + + virtual void reconstruct (idx_t key, float * recons) const override; + + virtual void merge_from_residuals (IndexIVF &other) override; + + + IndexIVFFlat () {} +}; + + + + + + +} // namespace faiss + + + + + +#endif diff --git a/IndexIVFPQ.cpp b/IndexIVFPQ.cpp new file mode 100644 index 000000000..6f60f60be --- /dev/null +++ b/IndexIVFPQ.cpp @@ -0,0 +1,1574 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +/* Copyright 2004-present Facebook. All Rights Reserved. + Inverted list structure. +*/ + +#include "IndexIVFPQ.h" + +#include +#include + +#include + +#include + +#include "Heap.h" +#include "utils.h" + +#include "Clustering.h" +#include "IndexFlat.h" + +#include "hamming.h" + +#include "FaissAssert.h" + +#include "AuxIndexStructures.h" + +namespace faiss { + + + + + +/***************************************** + * IndexIVFPQ implementation + ******************************************/ + +IndexIVFPQ::IndexIVFPQ (Index * quantizer, size_t d, size_t nlist, + size_t M, size_t nbits_per_idx): + IndexIVF (quantizer, d, nlist, METRIC_L2), + pq (d, M, nbits_per_idx) +{ + FAISS_ASSERT (nbits_per_idx <= 8); + code_size = pq.code_size; + is_trained = false; + codes.resize (nlist); + by_residual = true; + use_precomputed_table = 0; + scan_table_threshold = 0; + max_codes = 0; // means unlimited + + polysemous_training = nullptr; + do_polysemous_training = false; + polysemous_ht = 0; + + set_typename(); +} + + + +void IndexIVFPQ::set_typename () +{ + std::stringstream s; + s << "IvfPQ_" << pq.M << "x" << pq.nbits + << "[" << nlist << ":" << quantizer->index_typename << "]"; + index_typename = s.str(); +} + + +void IndexIVFPQ::train_residual (idx_t n, const float *x) +{ + train_residual_o (n, x, nullptr); +} + + +void IndexIVFPQ::train_residual_o (idx_t n, const float *x, float *residuals_2) +{ + idx_t ntrain = pq.ksub * 64; + if(n > ntrain) n = ntrain; + + const float *trainset; + if (by_residual) { + if(verbose) printf("computing residuals\n"); + idx_t * assign = new idx_t [n]; // assignement to coarse centroids + quantizer->assign (n, x, assign); + float *residuals = new float [n * d]; + for (idx_t i = 0; i < n; i++) + quantizer->compute_residual (x + i * d, residuals+i*d, assign[i]); + delete [] assign; + trainset = residuals; + } else { + trainset = x; + } + if (verbose) + printf ("training %zdx%zd product quantizer on %ld vectors in %dD\n", + pq.M, pq.ksub, n, d); + pq.verbose = verbose; + pq.train (n, trainset); + + if (do_polysemous_training) { + PolysemousTraining default_pt; + PolysemousTraining *pt = polysemous_training; + if (!pt) pt = &default_pt; + pt->optimize_pq_for_hamming (pq, n, trainset); + } + + // prepare second-level residuals for refine PQ + if (residuals_2) { + uint8_t *train_codes = new uint8_t [pq.code_size * n]; + pq.compute_codes (trainset, train_codes, n); + + for (idx_t i = 0; i < n; i++) { + const float *xx = trainset + i * d; + float * res = residuals_2 + i * d; + pq.decode (train_codes + i * pq.code_size, res); + for (int j = 0; j < d; j++) + res[j] = xx[j] - res[j]; + } + + delete [] train_codes; + } + + if (by_residual) { + delete [] trainset; + precompute_table (); + } + +} + + +/* produce a binary signature based on the residual vector */ +void IndexIVFPQ::encode (long key, const float * x, uint8_t * code) const +{ + if (by_residual) { + float residual_vec[d]; + quantizer->compute_residual (x, residual_vec, key); + pq.compute_code (residual_vec, code); + } + else pq.compute_code (x, code); +} + + + + + +void IndexIVFPQ::encode_multiple (size_t n, const long *keys, + const float * x, uint8_t * xcodes) const +{ + if (by_residual) { + float *residuals = new float [n * d]; + // TODO: parallelize? + for (size_t i = 0; i < n; i++) + quantizer->compute_residual (x + i * d, residuals + i * d, keys[i]); + pq.compute_codes (residuals, xcodes, n); + delete [] residuals; + } else { + pq.compute_codes (x, xcodes, n); + } +} + + + +void IndexIVFPQ::add_with_ids (idx_t n, const float * x, const long *xids) +{ + add_core_o (n, x, xids, nullptr); +} + + +void IndexIVFPQ::add_core_o (idx_t n, const float * x, const long *xids, + float *residuals_2, const long *precomputed_idx) +{ + FAISS_ASSERT (is_trained); + double t0 = getmillisecs (); + const long * idx; + + if (precomputed_idx) { + idx = precomputed_idx; + } else { + long * idx0 = new long [n]; + quantizer->assign (n, x, idx0); + idx = idx0; + } + + double t1 = getmillisecs (); + uint8_t * xcodes = new uint8_t [n * code_size]; + + const float *to_encode = nullptr; + + if (by_residual) { + float *residuals = new float [n * d]; + // TODO: parallelize? + for (size_t i = 0; i < n; i++) { + if (idx[i] < 0) + memset (residuals + i * d, 0, sizeof(*residuals) * d); + else + quantizer->compute_residual ( + x + i * d, residuals + i * d, idx[i]); + } + to_encode = residuals; + } else { + to_encode = x; + } + pq.compute_codes (to_encode, xcodes, n); + + double t2 = getmillisecs (); + // TODO: parallelize? + size_t n_ignore = 0; + for (size_t i = 0; i < n; i++) { + idx_t key = idx[i]; + if (key < 0) { + n_ignore ++; + if (residuals_2) + memset (residuals_2, 0, sizeof(*residuals_2) * d); + continue; + } + idx_t id = xids ? xids[i] : ntotal + i; + ids[key].push_back (id); + uint8_t *code = xcodes + i * code_size; + for (size_t j = 0; j < code_size; j++) + codes[key].push_back (code[j]); + + if (residuals_2) { + float *res2 = residuals_2 + i * d; + const float *xi = to_encode + i * d; + pq.decode (code, res2); + for (int j = 0; j < d; j++) + res2[j] = xi[j] - res2[j]; + } + + if (maintain_direct_map) + direct_map.push_back (key << 32 | (ids[key].size() - 1)); + } + + if (by_residual) + delete [] to_encode; + + delete [] xcodes; + if (!precomputed_idx) + delete [] idx; + double t3 = getmillisecs (); + if(verbose) { + char comment[100] = {0}; + if (n_ignore > 0) + snprintf (comment, 100, "(%ld vectors ignored)", n_ignore); + printf(" add_core times: %.3f %.3f %.3f %s\n", + t1 - t0, t2 - t1, t3 - t2, comment); + } + ntotal += n; +} + +void IndexIVFPQ::reconstruct_n (idx_t i0, idx_t ni, float *recons) const +{ + FAISS_ASSERT (ni == 0 || (i0 >= 0 && i0 + ni <= ntotal)); + + std::vector centroid (d); + + for (int key = 0; key < nlist; key++) { + const std::vector & idlist = ids[key]; + const uint8_t * code_line = codes[key].data(); + + for (long ofs = 0; ofs < idlist.size(); ofs++) { + long id = idlist[ofs]; + if (!(id >= i0 && id < i0 + ni)) continue; + float *r = recons + d * (id - i0); + if (by_residual) { + quantizer->reconstruct (key, centroid.data()); + pq.decode (code_line + ofs * pq.code_size, r); + for (int j = 0; j < d; j++) { + r[j] += centroid[j]; + } + } + else { + pq.decode (code_line + ofs * pq.code_size, r); + } + } + } +} + + +void IndexIVFPQ::reconstruct (idx_t key, float * recons) const +{ + FAISS_ASSERT (direct_map.size() == ntotal); + int list_no = direct_map[key] >> 32; + int ofs = direct_map[key] & 0xffffffff; + + quantizer->reconstruct (list_no, recons); + const uint8_t * code = &(codes[list_no][ofs * pq.code_size]); + + for (size_t m = 0; m < pq.M; m++) { + float * out = recons + m * pq.dsub; + const float * cent = pq.get_centroids (m, code[m]); + for (size_t i = 0; i < pq.dsub; i++) { + out[i] += cent[i]; + } + } +} + + + +void IndexIVFPQ::merge_from_residuals (IndexIVF &other_in) +{ + IndexIVFPQ &other = dynamic_cast (other_in); + for (int i = 0; i < nlist; i++) { + codes[i].insert (codes[i].end(), + other.codes[i].begin(), other.codes[i].end()); + other.codes[i].clear(); + } +} + +void IndexIVFPQ::copy_subset_to (IndexIVFPQ & other, int subset_type, + long a1, long a2) const +{ + FAISS_ASSERT (nlist == other.nlist); + FAISS_ASSERT (!other.maintain_direct_map); + size_t code_size = pq.code_size; + for (long list_no = 0; list_no < nlist; list_no++) { + const std::vector & ids_in = ids[list_no]; + std::vector & ids_out = other.ids[list_no]; + const std::vector & codes_in = codes[list_no]; + std::vector & codes_out = other.codes[list_no]; + + for (long i = 0; i < ids_in.size(); i++) { + idx_t id = ids_in[i]; + if (subset_type == 0 && a1 <= id && id < a2) { + ids_out.push_back (id); + codes_out.insert (codes_out.end(), + codes_in.begin() + i * code_size, + codes_in.begin() + (i + 1) * code_size); + other.ntotal++; + } + } + } +} + + + + + +/** Precomputed tables for residuals + * + * During IVFPQ search with by_residual, we compute + * + * d = || x - y_C - y_R ||^2 + * + * where x is the query vector, y_C the coarse centroid, y_R the + * refined PQ centroid. The expression can be decomposed as: + * + * d = || x - y_C ||^2 + || y_R ||^2 + 2 * (y_C|y_R) - 2 * (x|y_R) + * --------------- --------------------------- ------- + * term 1 term 2 term 3 + * + * When using multiprobe, we use the following decomposition: + * - term 1 is the distance to the coarse centroid, that is computed + * during the 1st stage search. + * - term 2 can be precomputed, as it does not involve x. However, + * because of the PQ, it needs nlist * M * ksub storage. This is why + * use_precomputed_table is off by default + * - term 3 is the classical non-residual distance table. + * + * Since y_R defined by a product quantizer, it is split across + * subvectors and stored separately for each subvector. If the coarse + * quantizer is a MultiIndexQuantizer then the table can be stored + * more compactly. + * + * At search time, the tables for term 2 and term 3 are added up. This + * is faster when the length of the lists is > ksub * M. + */ + +void IndexIVFPQ::precompute_table () +{ + + + if (use_precomputed_table == 0) { // then choose the type of table + if (quantizer->metric_type == METRIC_INNER_PRODUCT) { + fprintf(stderr, "IndexIVFPQ::precompute_table: WARN precomputed " + "tables not supported for inner product quantizers\n"); + return; + } + const MultiIndexQuantizer *miq = + dynamic_cast (quantizer); + if (miq && pq.M % miq->pq.M == 0) + use_precomputed_table = 2; + else + use_precomputed_table = 1; + } // otherwise assume user has set appropriate flag on input + + + // squared norms of the PQ centroids + std::vector r_norms (pq.M * pq.ksub, 0.0/0.0); + for (int m = 0; m < pq.M; m++) + for (int j = 0; j < pq.ksub; j++) + r_norms [m * pq.ksub + j] = + fvec_norm_L2sqr (pq.get_centroids (m, j), pq.dsub); + + if (use_precomputed_table == 1) { + + precomputed_table.resize (nlist * pq.M * pq.ksub); + std::vector centroid (d); + + for (size_t i = 0; i < nlist; i++) { + quantizer->reconstruct (i, centroid.data()); + + float *tab = &precomputed_table[i * pq.M * pq.ksub]; + pq.compute_inner_prod_table (centroid.data(), tab); + fvec_madd (pq.M * pq.ksub, r_norms.data(), 2.0, tab, tab); + } + } else if (use_precomputed_table == 2) { + const MultiIndexQuantizer *miq = + dynamic_cast (quantizer); + FAISS_ASSERT (miq); + const ProductQuantizer &cpq = miq->pq; + FAISS_ASSERT (pq.M % cpq.M == 0); + + precomputed_table.resize(cpq.ksub * pq.M * pq.ksub); + + // reorder PQ centroid table + std::vector centroids (d * cpq.ksub, 0.0/0.0); + + for (int m = 0; m < cpq.M; m++) { + for (size_t i = 0; i < cpq.ksub; i++) { + memcpy (centroids.data() + i * d + m * cpq.dsub, + cpq.get_centroids (m, i), + sizeof (*centroids.data()) * cpq.dsub); + } + } + + pq.compute_inner_prod_tables (cpq.ksub, centroids.data (), + precomputed_table.data ()); + + for (size_t i = 0; i < cpq.ksub; i++) { + float *tab = &precomputed_table[i * pq.M * pq.ksub]; + fvec_madd (pq.M * pq.ksub, r_norms.data(), 2.0, tab, tab); + } + + } +} + +namespace { + +static uint64_t get_cycles () { + uint32_t high, low; + asm volatile("rdtsc \n\t" + : "=a" (low), + "=d" (high)); + return ((uint64_t)high << 32) | (low); +} + +#define TIC t0 = get_cycles() +#define TOC get_cycles () - t0 + + + +/** QueryTables manages the various ways of searching an + * IndexIVFPQ. The code contains a lot of branches, depending on: + * - metric_type: are we computing L2 or Inner product similarity? + * - by_residual: do we encode raw vectors or residuals? + * - use_precomputed_table: are x_R|x_C tables precomputed? + * - polysemous_ht: are we filtering with polysemous codes? + */ +struct QueryTables { + + /***************************************************** + * General data from the IVFPQ + *****************************************************/ + + const IndexIVFPQ & ivfpq; + + // copied from IndexIVFPQ for easier access + int d; + const ProductQuantizer & pq; + MetricType metric_type; + bool by_residual; + int use_precomputed_table; + + // pre-allocated data buffers + float * sim_table, * sim_table_2; + float * residual_vec, *decoded_vec; + + // single data buffer + std::vector mem; + + // for table pointers + std::vector sim_table_ptrs; + + explicit QueryTables (const IndexIVFPQ & ivfpq): + ivfpq(ivfpq), + d(ivfpq.d), + pq (ivfpq.pq), + metric_type (ivfpq.metric_type), + by_residual (ivfpq.by_residual), + use_precomputed_table (ivfpq.use_precomputed_table) + { + mem.resize (pq.ksub * pq.M * 2 + d *2); + sim_table = mem.data(); + sim_table_2 = sim_table + pq.ksub * pq.M; + residual_vec = sim_table_2 + pq.ksub * pq.M; + decoded_vec = residual_vec + d; + + // for polysemous + if (ivfpq.polysemous_ht != 0) { + q_code.resize (pq.code_size); + } + init_list_cycles = 0; + sim_table_ptrs.resize (pq.M); + } + + /***************************************************** + * What we do when query is known + *****************************************************/ + + // field specific to query + const float * qi; + + // query-specific intialization + void init_query (const float * qi) { + this->qi = qi; + if (metric_type == METRIC_INNER_PRODUCT) + init_query_IP (); + else + init_query_L2 (); + if (!by_residual && ivfpq.polysemous_ht != 0) + pq.compute_code (qi, q_code.data()); + } + + void init_query_IP () { + // precompute some tables specific to the query qi + pq.compute_inner_prod_table (qi, sim_table); + // we compute negated inner products for use with the maxheap + for (int i = 0; i < pq.ksub * pq.M; i++) { + sim_table[i] = - sim_table[i]; + } + } + + void init_query_L2 () { + if (!by_residual) { + pq.compute_distance_table (qi, sim_table); + } else if (use_precomputed_table) { + pq.compute_inner_prod_table (qi, sim_table_2); + } + } + + /***************************************************** + * When inverted list is known: prepare computations + *****************************************************/ + + // fields specific to list + Index::idx_t key; + float coarse_dis; + std::vector q_code; + + uint64_t init_list_cycles; + + /// once we know the query and the centroid, we can prepare the + /// sim_table that will be used for accumulation + /// and dis0, the initial value + float precompute_list_tables () { + float dis0 = 0; + uint64_t t0; TIC; + if (by_residual) { + if (metric_type == METRIC_INNER_PRODUCT) + dis0 = precompute_list_tables_IP (); + else + dis0 = precompute_list_tables_L2 (); + } + init_list_cycles += TOC; + return dis0; + } + + float precompute_list_table_pointers () { + float dis0 = 0; + uint64_t t0; TIC; + if (by_residual) { + if (metric_type == METRIC_INNER_PRODUCT) + FAISS_ASSERT (!"not implemented"); + else + dis0 = precompute_list_table_pointers_L2 (); + } + init_list_cycles += TOC; + return dis0; + } + + /***************************************************** + * compute tables for inner prod + *****************************************************/ + + float precompute_list_tables_IP () + { + // prepare the sim_table that will be used for accumulation + // and dis0, the initial value + ivfpq.quantizer->reconstruct (key, decoded_vec); + // decoded_vec = centroid + float dis0 = -fvec_inner_product (qi, decoded_vec, d); + + if (ivfpq.polysemous_ht) { + for (int i = 0; i < d; i++) { + residual_vec [i] = qi[i] - decoded_vec[i]; + } + pq.compute_code (residual_vec, q_code.data()); + } + return dis0; + } + + + /***************************************************** + * compute tables for L2 distance + *****************************************************/ + + float precompute_list_tables_L2 () + { + float dis0 = 0; + + if (use_precomputed_table == 0) { + ivfpq.quantizer->compute_residual (qi, residual_vec, key); + pq.compute_distance_table (residual_vec, sim_table); + } else if (use_precomputed_table == 1) { + dis0 = coarse_dis; + + fvec_madd (pq.M * pq.ksub, + &ivfpq.precomputed_table [key * pq.ksub * pq.M], + -2.0, sim_table_2, + sim_table); + } else if (use_precomputed_table == 2) { + dis0 = coarse_dis; + + const MultiIndexQuantizer *miq = + dynamic_cast (ivfpq.quantizer); + FAISS_ASSERT (miq); + const ProductQuantizer &cpq = miq->pq; + int Mf = pq.M / cpq.M; + + const float *qtab = sim_table_2; // query-specific table + float *ltab = sim_table; // (output) list-specific table + + long k = key; + for (int cm = 0; cm < cpq.M; cm++) { + // compute PQ index + int ki = k & ((uint64_t(1) << cpq.nbits) - 1); + k >>= cpq.nbits; + + // get corresponding table + const float *pc = &ivfpq.precomputed_table + [(ki * pq.M + cm * Mf) * pq.ksub]; + + if (ivfpq.polysemous_ht == 0) { + + // sum up with query-specific table + fvec_madd (Mf * pq.ksub, + pc, + -2.0, qtab, + ltab); + ltab += Mf * pq.ksub; + qtab += Mf * pq.ksub; + } else { + for (int m = cm * Mf; m < (cm + 1) * Mf; m++) { + q_code[m] = fvec_madd_and_argmin + (pq.ksub, pc, -2, qtab, ltab); + pc += pq.ksub; + ltab += pq.ksub; + qtab += pq.ksub; + } + } + + } + } + + return dis0; + } + + float precompute_list_table_pointers_L2 () + { + float dis0 = 0; + + if (use_precomputed_table == 1) { + dis0 = coarse_dis; + + const float * s = &ivfpq.precomputed_table [key * pq.ksub * pq.M]; + for (int m = 0; m < pq.M; m++) { + sim_table_ptrs [m] = s; + s += pq.ksub; + } + } else if (use_precomputed_table == 2) { + dis0 = coarse_dis; + + const MultiIndexQuantizer *miq = + dynamic_cast (ivfpq.quantizer); + FAISS_ASSERT (miq); + const ProductQuantizer &cpq = miq->pq; + int Mf = pq.M / cpq.M; + + long k = key; + int m0 = 0; + for (int cm = 0; cm < cpq.M; cm++) { + int ki = k & ((uint64_t(1) << cpq.nbits) - 1); + k >>= cpq.nbits; + + const float *pc = &ivfpq.precomputed_table + [(ki * pq.M + cm * Mf) * pq.ksub]; + + for (int m = m0; m < m0 + Mf; m++) { + sim_table_ptrs [m] = pc; + pc += pq.ksub; + } + m0 += Mf; + } + } else FAISS_ASSERT (!"need precomputed tables"); + + if (ivfpq.polysemous_ht) { + FAISS_ASSERT (!"not implemented"); + // Not clear that it makes sense to implemente this, + // because it costs M * ksub, which is what we wanted to + // avoid with the tables pointers. + } + + return dis0; + } + + +}; + + +/***************************************************** + * Scaning the codes. + * The scanning functions call their favorite precompute_* + * function to precompute the tables they need. + *****************************************************/ +template +struct InvertedListScanner: QueryTables { + + const uint8_t * __restrict list_codes; + const IDType * list_ids; + size_t list_size; + + explicit InvertedListScanner (const IndexIVFPQ & ivfpq): + QueryTables (ivfpq) + { + FAISS_ASSERT(pq.byte_per_idx == 1); + n_hamming_pass = 0; + } + + /// list_specific intialization + void init_list (Index::idx_t key, float coarse_dis, + size_t list_size_in, const IDType *list_ids_in, + const uint8_t *list_codes_in) { + this->key = key; + this->coarse_dis = coarse_dis; + list_size = list_size_in; + list_codes = list_codes_in; + list_ids = list_ids_in; + } + + /***************************************************** + * Scaning the codes: simple PQ scan. + *****************************************************/ + + /// version of the scan where we use precomputed tables + void scan_list_with_table ( + size_t k, float * heap_sim, long * heap_ids, bool store_pairs) + { + float dis0 = precompute_list_tables (); + + for (size_t j = 0; j < list_size; j++) { + + float dis = dis0; + const float *tab = sim_table; + + for (size_t m = 0; m < pq.M; m++) { + dis += tab[*list_codes++]; + tab += pq.ksub; + } + + if (dis < heap_sim[0]) { + maxheap_pop (k, heap_sim, heap_ids); + long id = store_pairs ? (key << 32 | j) : list_ids[j]; + maxheap_push (k, heap_sim, heap_ids, dis, id); + } + } + } + + + /// tables are not precomputed, but pointers are provided to the + /// relevant X_c|x_r tables + void scan_list_with_pointer ( + size_t k, float * heap_sim, long * heap_ids, bool store_pairs) + { + + float dis0 = precompute_list_table_pointers (); + + for (size_t j = 0; j < list_size; j++) { + + float dis = dis0; + const float *tab = sim_table_2; + + for (size_t m = 0; m < pq.M; m++) { + int ci = *list_codes++; + dis += sim_table_ptrs [m][ci] - 2 * tab [ci]; + tab += pq.ksub; + } + + if (dis < heap_sim[0]) { + maxheap_pop (k, heap_sim, heap_ids); + long id = store_pairs ? (key << 32 | j) : list_ids[j]; + maxheap_push (k, heap_sim, heap_ids, dis, id); + } + } + + } + + /// nothing is precomputed: access residuals on-the-fly + void scan_on_the_fly_dist ( + size_t k, float * heap_sim, long * heap_ids, bool store_pairs) + { + + if (by_residual && use_precomputed_table) { + scan_list_with_pointer (k, heap_sim, heap_ids, store_pairs); + return; + } + + const float *dvec; + float dis0 = 0; + + if (by_residual) { + if (metric_type == METRIC_INNER_PRODUCT) { + ivfpq.quantizer->reconstruct (key, residual_vec); + dis0 = fvec_inner_product (residual_vec, qi, d); + } else { + ivfpq.quantizer->compute_residual (qi, residual_vec, key); + } + dvec = residual_vec; + } else { + dvec = qi; + dis0 = 0; + } + + for (size_t j = 0; j < list_size; j++) { + + pq.decode (list_codes, decoded_vec); + list_codes += pq.code_size; + + float dis; + if (metric_type == METRIC_INNER_PRODUCT) { + dis = -dis0 - fvec_inner_product (decoded_vec, qi, d); + } else { + dis = fvec_L2sqr (decoded_vec, dvec, d); + } + + if (dis < heap_sim[0]) { + maxheap_pop (k, heap_sim, heap_ids); + long id = store_pairs ? (key << 32 | j) : list_ids[j]; + maxheap_push (k, heap_sim, heap_ids, dis, id); + } + } + } + + /***************************************************** + * Scanning codes with polysemous filtering + *****************************************************/ + + // code for the query + size_t n_hamming_pass; + + + template + void scan_list_polysemous_hc ( + size_t k, float * heap_sim, long * heap_ids, bool store_pairs) + { + float dis0 = precompute_list_tables (); + int ht = ivfpq.polysemous_ht; + + int code_size = pq.code_size; + + HammingComputer hc (q_code.data(), code_size); + + for (size_t j = 0; j < list_size; j++) { + const uint8_t *b_code = list_codes; + int hd = hc.hamming (b_code); + if (hd < ht) { + n_hamming_pass ++; + + float dis = dis0; + const float *tab = sim_table; + + for (size_t m = 0; m < pq.M; m++) { + dis += tab[*b_code++]; + tab += pq.ksub; + } + + if (dis < heap_sim[0]) { + maxheap_pop (k, heap_sim, heap_ids); + long id = store_pairs ? (key << 32 | j) : list_ids[j]; + maxheap_push (k, heap_sim, heap_ids, dis, id); + } + } + list_codes += code_size; + } + } + + void scan_list_polysemous ( + size_t k, float * heap_sim, long * heap_ids, bool store_pairs) + { + switch (pq.code_size) { +#define HANDLE_CODE_SIZE(cs) \ + case cs: \ + scan_list_polysemous_hc \ + (k, heap_sim, heap_ids, store_pairs); \ + break + HANDLE_CODE_SIZE(4); + HANDLE_CODE_SIZE(8); + HANDLE_CODE_SIZE(16); + HANDLE_CODE_SIZE(20); + HANDLE_CODE_SIZE(32); + HANDLE_CODE_SIZE(64); +#undef HANDLE_CODE_SIZE + default: + if (pq.code_size % 8 == 0) + scan_list_polysemous_hc + (k, heap_sim, heap_ids, store_pairs); + else + scan_list_polysemous_hc + (k, heap_sim, heap_ids, store_pairs); + break; + } + } + +}; + + + + +} // anonymous namespace + + +IndexIVFPQStats indexIVFPQ_stats; + +void IndexIVFPQStats::reset () { + memset (this, 0, sizeof (*this)); +} + + +void IndexIVFPQ::search_knn_with_key ( + size_t nx, + const float * qx, + const long * keys, + const float * coarse_dis, + float_maxheap_array_t * res, + bool store_pairs) const +{ + const size_t k = res->k; + +#pragma omp parallel + { + InvertedListScanner qt (*this); + size_t stats_nlist = 0; + size_t stats_ncode = 0; + uint64_t init_query_cycles = 0; + uint64_t scan_cycles = 0; + uint64_t heap_cycles = 0; + +#pragma omp for + for (size_t i = 0; i < nx; i++) { + const float *qi = qx + i * d; + const long * keysi = keys + i * nprobe; + const float *coarse_dis_i = coarse_dis + i * nprobe; + float * heap_sim = res->get_val (i); + long * heap_ids = res->get_ids (i); + + uint64_t t0; + TIC; + maxheap_heapify (k, heap_sim, heap_ids); + heap_cycles += TOC; + + TIC; + qt.init_query (qi); + init_query_cycles += TOC; + + size_t nscan = 0; + + for (size_t ik = 0; ik < nprobe; ik++) { + long key = keysi[ik]; /* select the list */ + if (key < 0) { + // not enough centroids for multiprobe + continue; + } + if (key >= (long) nlist) { + fprintf (stderr, "Invalid key=%ld nlist=%ld\n", key, nlist); + throw; + } + size_t list_size = ids[key].size(); + stats_nlist ++; + nscan += list_size; + + if (list_size == 0) continue; + + qt.init_list (key, coarse_dis_i[ik], + list_size, ids[key].data(), + codes[key].data()); + + TIC; + if (polysemous_ht > 0) { + qt.scan_list_polysemous + (k, heap_sim, heap_ids, store_pairs); + } else if (list_size > scan_table_threshold) { + qt.scan_list_with_table (k, heap_sim, heap_ids, store_pairs); + } else { + qt.scan_on_the_fly_dist (k, heap_sim, heap_ids, store_pairs); + } + scan_cycles += TOC; + + if (max_codes && nscan >= max_codes) break; + } + stats_ncode += nscan; + TIC; + maxheap_reorder (k, heap_sim, heap_ids); + + if (metric_type == METRIC_INNER_PRODUCT) { + for (size_t j = 0; j < k; j++) + heap_sim[j] = -heap_sim[j]; + } + heap_cycles += TOC; + } + +#pragma omp critical + { + indexIVFPQ_stats.n_hamming_pass += qt.n_hamming_pass; + indexIVFPQ_stats.nlist += stats_nlist; + indexIVFPQ_stats.ncode += stats_ncode; + + indexIVFPQ_stats.init_query_cycles += init_query_cycles; + indexIVFPQ_stats.init_list_cycles += qt.init_list_cycles; + indexIVFPQ_stats.scan_cycles += scan_cycles - qt.init_list_cycles; + indexIVFPQ_stats.heap_cycles += heap_cycles; + } + + } + indexIVFPQ_stats.nq += nx; +} + + +void IndexIVFPQ::search (idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const +{ + long * idx = new long [n * nprobe]; + float * coarse_dis = new float [n * nprobe]; + uint64_t t0; + TIC; + quantizer->search (n, x, nprobe, coarse_dis, idx); + indexIVFPQ_stats.assign_cycles += TOC; + + TIC; + float_maxheap_array_t res = { size_t(n), size_t(k), labels, distances}; + + search_knn_with_key (n, x, idx, coarse_dis, &res); + delete [] idx; + delete [] coarse_dis; + indexIVFPQ_stats.search_cycles += TOC; +} + + +void IndexIVFPQ::reset() +{ + IndexIVF::reset(); + for (size_t key = 0; key < nlist; key++) { + codes[key].clear(); + } +} + +long IndexIVFPQ::remove_ids (const IDSelector & sel) +{ + FAISS_ASSERT (!maintain_direct_map || + !"direct map remove not implemented"); + long nremove = 0; +#pragma omp parallel for reduction(+: nremove) + for (long i = 0; i < nlist; i++) { + std::vector & idsi = ids[i]; + uint8_t * codesi = codes[i].data(); + + long l = idsi.size(), j = 0; + while (j < l) { + if (sel.is_member (idsi[j])) { + l--; + idsi [j] = idsi [l]; + memmove (codesi + j * code_size, + codesi + l * code_size, code_size); + } else { + j++; + } + } + if (l < idsi.size()) { + nremove += idsi.size() - l; + idsi.resize (l); + codes[i].resize (l * code_size); + } + } + ntotal -= nremove; + return nremove; +} + + +IndexIVFPQ::IndexIVFPQ () +{ + // initialize some runtime values + use_precomputed_table = 0; + scan_table_threshold = 0; + do_polysemous_training = false; + polysemous_ht = 0; + max_codes = 0; + polysemous_training = nullptr; +} + + +struct CodeCmp { + const uint8_t *tab; + size_t code_size; + bool operator () (int a, int b) const { + return cmp (a, b) > 0; + } + int cmp (int a, int b) const { + return memcmp (tab + a * code_size, tab + b * code_size, + code_size); + } +}; + + +size_t IndexIVFPQ::find_duplicates (idx_t *dup_ids, size_t *lims) const +{ + size_t ngroup = 0; + lims[0] = 0; + for (size_t list_no = 0; list_no < nlist; list_no++) { + size_t n = ids[list_no].size(); + std::vector ord (n); + for (int i = 0; i < n; i++) ord[i] = i; + CodeCmp cs = { codes[list_no].data(), code_size }; + std::sort (ord.begin(), ord.end(), cs); + + const idx_t *list_ids = ids[list_no].data(); + int prev = -1; // all elements from prev to i-1 are equal + for (int i = 0; i < n; i++) { + if (prev >= 0 && cs.cmp (ord [prev], ord [i]) == 0) { + // same as previous => remember + if (prev + 1 == i) { // start new group + ngroup++; + lims[ngroup] = lims[ngroup - 1]; + dup_ids [lims [ngroup]++] = list_ids [ord [prev]]; + } + dup_ids [lims [ngroup]++] = list_ids [ord [i]]; + } else { // not same as previous. + prev = i; + } + } + } + return ngroup; +} + + + + +/***************************************** + * IndexIVFPQR implementation + ******************************************/ + +IndexIVFPQR::IndexIVFPQR ( + Index * quantizer, size_t d, size_t nlist, + size_t M, size_t nbits_per_idx, + size_t M_refine, size_t nbits_per_idx_refine): + IndexIVFPQ (quantizer, d, nlist, M, nbits_per_idx), + refine_pq (d, M_refine, nbits_per_idx_refine), + k_factor (4) +{ + by_residual = true; + set_typename(); +} + +IndexIVFPQR::IndexIVFPQR (): + k_factor (1) +{ + by_residual = true; +} + + +void IndexIVFPQR::set_typename() +{ + std::stringstream s; + s << "IvfPQR_" << pq.M << "x" << pq.nbits + << "+" << refine_pq.M << "x" << refine_pq.nbits + << "[" << nlist << ":" << quantizer->index_typename << "]"; + index_typename = s.str(); + +} + + +void IndexIVFPQR::reset() +{ + IndexIVFPQ::reset(); + refine_codes.clear(); +} + + + + +void IndexIVFPQR::train_residual (idx_t n, const float *x) +{ + + float * residual_2 = new float [n * d]; + + train_residual_o (n, x, residual_2); + + if (verbose) + printf ("training %zdx%zd 2nd level PQ quantizer on %ld %dD-vectors\n", + refine_pq.M, refine_pq.ksub, n, d); + + refine_pq.cp.max_points_per_centroid = 1000; + refine_pq.cp.verbose = verbose; + + refine_pq.train (n, residual_2); + delete [] residual_2; + +} + + +void IndexIVFPQR::add_with_ids (idx_t n, const float *x, const long *xids) { + add_core (n, x, xids, nullptr); +} + +void IndexIVFPQR::add_core (idx_t n, const float *x, const long *xids, + const long *precomputed_idx) { + + float * residual_2 = new float [n * d]; + + idx_t n0 = ntotal; + + add_core_o (n, x, xids, residual_2, precomputed_idx); + + refine_codes.resize (ntotal * refine_pq.code_size); + + refine_pq.compute_codes ( + residual_2, &refine_codes[n0 * refine_pq.code_size], n); + + delete [] residual_2; + +} + + +void IndexIVFPQR::search ( + idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const +{ + FAISS_ASSERT (is_trained); + long * idx = new long [n * nprobe]; + float * L1_dis = new float [n * nprobe]; + uint64_t t0; + TIC; + quantizer->search (n, x, nprobe, L1_dis, idx); + indexIVFPQ_stats.assign_cycles += TOC; + + TIC; + size_t k_coarse = long(k * k_factor); + idx_t *coarse_labels = new idx_t [k_coarse * n]; + { // query with quantizer levels 1 and 2. + float *coarse_distances = new float [k_coarse * n]; + + faiss::float_maxheap_array_t res_coarse = { + size_t(n), k_coarse, coarse_labels, coarse_distances}; + search_knn_with_key (n, x, idx, L1_dis, &res_coarse, true); + delete [] coarse_distances; + } + delete [] L1_dis; + indexIVFPQ_stats.search_cycles += TOC; + + TIC; + + // 3rd level refinement + size_t n_refine = 0; +#pragma omp parallel reduction(+ : n_refine) + { + // tmp buffers + float *residual_1 = new float [2 * d]; + float *residual_2 = residual_1 + d; +#pragma omp for + for (idx_t i = 0; i < n; i++) { + const float *xq = x + i * d; + const long * shortlist = coarse_labels + k_coarse * i; + float * heap_sim = distances + k * i; + long * heap_ids = labels + k * i; + maxheap_heapify (k, heap_sim, heap_ids); + + for (int j = 0; j < k_coarse; j++) { + long sl = shortlist[j]; + + if (sl == -1) continue; + + int list_no = sl >> 32; + int ofs = sl & 0xffffffff; + + assert (list_no >= 0 && list_no < nlist); + assert (ofs >= 0 && ofs < ids[list_no].size()); + + // 1st level residual + quantizer->compute_residual (xq, residual_1, list_no); + + // 2nd level residual + const uint8_t * l2code = &codes[list_no][ofs * pq.code_size]; + pq.decode (l2code, residual_2); + for (int l = 0; l < d; l++) + residual_2[l] = residual_1[l] - residual_2[l]; + + // 3rd level residual's approximation + idx_t id = ids[list_no][ofs]; + assert (0 <= id && id < ntotal); + refine_pq.decode (&refine_codes [id * refine_pq.code_size], + residual_1); + + float dis = fvec_L2sqr (residual_1, residual_2, d); + + if (dis < heap_sim[0]) { + maxheap_pop (k, heap_sim, heap_ids); + maxheap_push (k, heap_sim, heap_ids, dis, id); + } + n_refine ++; + } + maxheap_reorder (k, heap_sim, heap_ids); + } + delete [] residual_1; + } + delete [] coarse_labels; + delete [] idx; + indexIVFPQ_stats.nrefine += n_refine; + indexIVFPQ_stats.refine_cycles += TOC; +} + +void IndexIVFPQR::reconstruct_n (idx_t i0, idx_t ni, float *recons) const +{ + float *r3 = new float [d]; + + IndexIVFPQ::reconstruct_n (i0, ni, recons); + + for (idx_t i = i0; i < i0 + ni; i++) { + float *r = recons + i * d; + refine_pq.decode (&refine_codes [i * refine_pq.code_size], r3); + + for (int j = 0; j < d; j++) + r[j] += r3[j]; + + } + delete [] r3; +} + +void IndexIVFPQR::merge_from_residuals (IndexIVF &other_in) +{ + IndexIVFPQR &other = dynamic_cast (other_in); + IndexIVFPQ::merge_from_residuals (other); + refine_codes.insert (refine_codes.end(), + other.refine_codes.begin(), other.refine_codes.end()); + other.refine_codes.clear(); +} + +long IndexIVFPQR::remove_ids (const IDSelector & sel) +{ + FAISS_ASSERT(!"not implemented"); +} + +/***************************************** + * IndexIVFPQCompact implementation + ******************************************/ + +IndexIVFPQCompact::IndexIVFPQCompact () +{ + alloc_type = Alloc_type_none; + limits = nullptr; + compact_ids = nullptr; + compact_codes = nullptr; +} + + +IndexIVFPQCompact::IndexIVFPQCompact (const IndexIVFPQ &other) +{ + FAISS_ASSERT (other.ntotal < (1UL << 31) || + !"IndexIVFPQCompact cannot store more than 2G images"); + + // here it would be more convenient to just use the + // copy-constructor, but it would copy the lists as well: too much + // overhead... + + // copy fields from Index + d = other.d; + ntotal = other.ntotal; + verbose = other.verbose; + is_trained = other.is_trained; + metric_type = other.metric_type; + + // copy fields from IndexIVF (except ids) + nlist = other.nlist; + nprobe = other.nprobe; + quantizer = other.quantizer; + quantizer_trains_alone = other.quantizer_trains_alone; + own_fields = false; + direct_map = other.direct_map; + + // copy fields from IndexIVFPQ (except codes) + by_residual = other.by_residual; + use_precomputed_table = other.use_precomputed_table; + precomputed_table = other.precomputed_table; + code_size = other.code_size; + pq = other.pq; + do_polysemous_training = other.do_polysemous_training; + polysemous_training = nullptr; + + scan_table_threshold = other.scan_table_threshold; + max_codes = other.max_codes; + polysemous_ht = other.polysemous_ht; + + //allocate + alloc_type = Alloc_type_new; + limits = new uint32_t [nlist + 1]; + compact_ids = new uint32_t [ntotal]; + compact_codes = new uint8_t [ntotal * code_size]; + + + // copy content from other + size_t ofs = 0; + for (size_t i = 0; i < nlist; i++) { + limits [i] = ofs; + const std::vector &other_ids = other.ids[i]; + for (size_t j = 0; j < other_ids.size(); j++) { + long id = other_ids[j]; + FAISS_ASSERT (id < (1UL << 31) || + !"IndexIVFPQCompact cannot store ids > 2G"); + compact_ids[ofs + j] = id; + } + memcpy (compact_codes + ofs * code_size, + other.codes[i].data(), + other.codes[i].size()); + ofs += other_ids.size(); + } + FAISS_ASSERT (ofs == ntotal); + limits [nlist] = ofs; + +} + +void IndexIVFPQCompact::add (idx_t, const float *) { + FAISS_ASSERT (!"cannot add to an IndexIVFPQCompact"); +} + +void IndexIVFPQCompact::reset () { + FAISS_ASSERT (!"cannot reset an IndexIVFPQCompact"); +} + +void IndexIVFPQCompact::train (idx_t, const float *) { + FAISS_ASSERT (!"cannot train an IndexIVFPQCompact"); +} + + + + +IndexIVFPQCompact::~IndexIVFPQCompact () +{ + if (alloc_type == Alloc_type_new) { + delete [] limits; + delete [] compact_codes; + delete [] compact_ids; + } else if (alloc_type == Alloc_type_mmap) { + munmap (mmap_buffer, mmap_length); + + } + +} + +void IndexIVFPQCompact::search_knn_with_key ( + size_t nx, + const float * qx, + const long * keys, + const float * coarse_dis, + float_maxheap_array_t * res, + bool store_pairs) const +{ + const size_t k = res->k; + +#pragma omp parallel + { + InvertedListScanner qt (*this); + size_t stats_nlist = 0; + size_t stats_ncode = 0; + uint64_t init_query_cycles = 0; + uint64_t scan_cycles = 0; + uint64_t heap_cycles = 0; + +#pragma omp for + for (size_t i = 0; i < nx; i++) { + const float *qi = qx + i * d; + const long * keysi = keys + i * nprobe; + const float *coarse_dis_i = coarse_dis + i * nprobe; + float * heap_sim = res->get_val (i); + long * heap_ids = res->get_ids (i); + + uint64_t t0; + TIC; + maxheap_heapify (k, heap_sim, heap_ids); + heap_cycles += TOC; + + TIC; + qt.init_query (qi); + init_query_cycles += TOC; + + size_t nscan = 0; + + for (size_t ik = 0; ik < nprobe; ik++) { + long key = keysi[ik]; /* select the list */ + if (key < 0) { + // not enough centroids for multiprobe + continue; + } + if (key >= (long) nlist) { + fprintf (stderr, "Invalid key=%ld nlist=%ld\n", key, nlist); + throw; + } + size_t list_size = limits[key + 1] - limits[key]; + stats_nlist ++; + nscan += list_size; + + if (list_size == 0) continue; + + qt.init_list (key, coarse_dis_i[ik], + list_size, compact_ids + limits[key], + compact_codes + limits[key] * code_size); + + TIC; + if (polysemous_ht > 0) { + qt.scan_list_polysemous + (k, heap_sim, heap_ids, store_pairs); + } else if (list_size > scan_table_threshold) { + qt.scan_list_with_table (k, heap_sim, heap_ids, store_pairs); + } else { + qt.scan_on_the_fly_dist (k, heap_sim, heap_ids, store_pairs); + } + scan_cycles += TOC; + + if (max_codes && nscan >= max_codes) break; + } + stats_ncode += nscan; + TIC; + maxheap_reorder (k, heap_sim, heap_ids); + + if (metric_type == METRIC_INNER_PRODUCT) { + for (size_t j = 0; j < k; j++) { + heap_sim[i] = -heap_sim[i]; + } + } + heap_cycles += TOC; + } + +#pragma omp critical + { + indexIVFPQ_stats.n_hamming_pass += qt.n_hamming_pass; + indexIVFPQ_stats.nlist += stats_nlist; + indexIVFPQ_stats.ncode += stats_ncode; + + indexIVFPQ_stats.init_query_cycles += init_query_cycles; + indexIVFPQ_stats.init_list_cycles += qt.init_list_cycles; + indexIVFPQ_stats.scan_cycles += scan_cycles - qt.init_list_cycles; + indexIVFPQ_stats.heap_cycles += heap_cycles; + } + + } + indexIVFPQ_stats.nq += nx; +} + + + +} // namespace faiss diff --git a/IndexIVFPQ.h b/IndexIVFPQ.h new file mode 100644 index 000000000..567cac45f --- /dev/null +++ b/IndexIVFPQ.h @@ -0,0 +1,284 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved. +// -*- c++ -*- + +#ifndef FAISS_INDEX_IVFPQ_H +#define FAISS_INDEX_IVFPQ_H + + +#include + +#include "IndexIVF.h" +#include "IndexPQ.h" + + +namespace faiss { + + + +/** Inverted file with Product Quantizer encoding. Each residual + * vector is encoded as a product quantizer code. + */ +struct IndexIVFPQ: IndexIVF { + bool by_residual; ///< Encode residual or plain vector? + int use_precomputed_table; ///< if by_residual, build precompute tables + size_t code_size; ///< code size per vector in bytes + ProductQuantizer pq; ///< produces the codes + + bool do_polysemous_training; ///< reorder PQ centroids after training? + PolysemousTraining *polysemous_training; ///< if NULL, use default + + // search-time parameters + size_t scan_table_threshold; ///< use table computation or on-the-fly? + size_t max_codes; ///< max nb of codes to visit to do a query + int polysemous_ht; ///< Hamming thresh for polysemous filtering + + std::vector < std::vector > codes; // binary codes, size nlist + + /// if use_precompute_table + /// size nlist * pq.M * pq.ksub + std::vector precomputed_table; + + IndexIVFPQ ( + Index * quantizer, size_t d, size_t nlist, + size_t M, size_t nbits_per_idx); + + virtual void set_typename () override; + + virtual void add_with_ids ( + idx_t n, const float *x, + const long *xids = nullptr) override; + + /// same as add_core, also: + /// - output 2nd level residuals if residuals_2 != NULL + /// - use precomputed list numbers if precomputed_idx != NULL + void add_core_o (idx_t n, const float *x, + const long *xids, float *residuals_2, + const long *precomputed_idx = nullptr); + + virtual void search ( + idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const override; + + virtual void reset () override; + + virtual long remove_ids (const IDSelector & sel) override; + + /// trains the product quantizer + virtual void train_residual(idx_t n, const float *x) override; + + /// same as train_residual, also output 2nd level residuals + void train_residual_o (idx_t n, const float *x, float *residuals_2); + + + /** Reconstruct a subset of the indexed vectors + * + * @param i0 first vector to reconstruct + * @param ni nb of vectors to reconstruct + * @param recons output array of reconstructed vectors, size ni * d + */ + virtual void reconstruct_n (idx_t i0, idx_t ni, float *recons) + const override; + + virtual void reconstruct (idx_t key, float * recons) + const override; + + /** Find exact duplicates in the dataset. + * + * the duplicates are returned in pre-allocated arrays (see the + * max sizes). + * + * @params lims limits between groups of duplicates + * (max size ntotal / 2 + 1) + * @params ids ids[lims[i]] : ids[lims[i+1]-1] is a group of + * duplicates (max size ntotal) + * @return n number of groups found + */ + size_t find_duplicates (idx_t *ids, size_t *lims) const; + + // map a vector to a binary code knowning the index + void encode (long key, const float * x, uint8_t * code) const; + + /// same as encode, for multiple points at once + void encode_multiple (size_t n, const long *keys, + const float * x, uint8_t * codes) const; + + /** search a set of vectors, that are pre-quantized by the IVF + * quantizer. Fill in the corresponding heaps with the query + * results. + * + * @param nx nb of vectors to query + * @param qx query vectors, size nx * d + * @param keys coarse quantization indices, size nx * nprobe + * @param coarse_dis + * distances to coarse centroids, size nx * nprobe + * @param res heaps for all the results, gives the nprobe + * @param store_pairs store inv list index + inv list offset + * instead in upper/lower 32 bit of result, + * instead of ids (used for reranking). + */ + virtual void search_knn_with_key ( + size_t nx, + const float * qx, + const long * keys, + const float * coarse_dis, + float_maxheap_array_t* res, + bool store_pairs = false) const; + + /// build precomputed table + void precompute_table (); + + /// used to implement merging + virtual void merge_from_residuals (IndexIVF &other) override; + + + /** copy a subset of the entries index to the other index + * + * if subset_type == 0: copies ids in [a1, a2) + * if subset_type == 1: copies ids if id % a1 == a2 + */ + void copy_subset_to (IndexIVFPQ & other, int subset_type, + long a1, long a2) const; + + IndexIVFPQ (); + +}; + + +/// statistics are robust to internal threading, but not if +/// IndexIVFPQ::search is called by multiple threads +struct IndexIVFPQStats { + size_t nq; // nb of queries run + size_t nlist; // nb of inverted lists scanned + size_t ncode; // nb of codes visited + size_t nrefine; // nb of refines (IVFPQR) + + size_t n_hamming_pass; + // nb of passed Hamming distance tests (for polysemous) + + // timings measured with the CPU RTC + // on all threads + size_t assign_cycles; + size_t search_cycles; + size_t refine_cycles; // only for IVFPQR + + // single thread (double-counted with search_cycles) + size_t init_query_cycles; + size_t init_list_cycles; + size_t scan_cycles; + size_t heap_cycles; + + IndexIVFPQStats () {reset (); } + void reset (); +}; + +// global var that collects them all +extern IndexIVFPQStats indexIVFPQ_stats; + + + +/** Index with an additional level of PQ refinement */ +struct IndexIVFPQR: IndexIVFPQ { + ProductQuantizer refine_pq; ///< 3rd level quantizer + std::vector refine_codes; ///< corresponding codes + + /// factor between k requested in search and the k requested from the IVFPQ + float k_factor; + + IndexIVFPQR ( + Index * quantizer, size_t d, size_t nlist, + size_t M, size_t nbits_per_idx, + size_t M_refine, size_t nbits_per_idx_refine); + + virtual void set_typename () override; + + virtual void reset() override; + + virtual long remove_ids (const IDSelector & sel) override; + + /// trains the two product quantizers + virtual void train_residual (idx_t n, const float *x) override; + + virtual void add_with_ids (idx_t n, const float *x, const long *xids) + override; + + /// same as add_with_ids, but optionally use the precomputed list ids + void add_core (idx_t n, const float *x, const long *xids, + const long *precomputed_idx = nullptr); + + + virtual void reconstruct_n (idx_t i0, idx_t ni, float *recons) + const override; + + virtual void search ( + idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const override; + + virtual void merge_from_residuals (IndexIVF &other) override; + + IndexIVFPQR(); +}; + + +/** Index with 32-bit ids and flat tables. Must be constructed from an + * exisiting IndexIVFPQ. Cannot be copy-constructed/assigned. The + * actual data is stored in the compact_* tables, the ids and codes + * tables are not used. */ +struct IndexIVFPQCompact: IndexIVFPQ { + + explicit IndexIVFPQCompact (const IndexIVFPQ &other); + + /// how were the compact tables allocated? + enum Alloc_type_t { + Alloc_type_none, ///< alloc from outside + Alloc_type_new, ///< was allocated with new + Alloc_type_mmap ///< was mmapped + }; + + Alloc_type_t alloc_type; + + uint32_t *limits; ///< size nlist + 1 + uint32_t *compact_ids; ///< size ntotal + uint8_t *compact_codes; ///< size ntotal * code_size + + // file and buffer this was mmapped (will be unmapped when object + // is deleted) + char * mmap_buffer; + long mmap_length; + + virtual void search_knn_with_key ( + size_t nx, + const float * qx, + const long * keys, + const float * coarse_dis, + float_maxheap_array_t * res, + bool store_pairs = false) const override; + + /// the three following functions will fail at runtime + virtual void add (idx_t, const float *) override; + virtual void reset () override; + virtual void train (idx_t, const float *) override; + + virtual ~IndexIVFPQCompact (); + + IndexIVFPQCompact (); + +}; + + + +} // namespace faiss + + + + + +#endif diff --git a/IndexLSH.cpp b/IndexLSH.cpp new file mode 100644 index 000000000..d64cff929 --- /dev/null +++ b/IndexLSH.cpp @@ -0,0 +1,189 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "IndexLSH.h" + +#include +#include + +#include + +#include "utils.h" +#include "hamming.h" +#include "FaissAssert.h" + +namespace faiss { + +/*************************************************************** + * IndexLSH + ***************************************************************/ + + +IndexLSH::IndexLSH (idx_t d, int nbits, bool rotate_data, bool train_thresholds): + Index(d), nbits(nbits), rotate_data(rotate_data), + train_thresholds (train_thresholds), rrot(d, nbits) +{ + is_trained = !train_thresholds; + + bytes_per_vec = (nbits + 7) / 8; + + if (rotate_data) { + rrot.init(5); + } else { + FAISS_ASSERT(d >= nbits); + } + set_typename(); +} + +IndexLSH::IndexLSH (): + nbits (0), bytes_per_vec(0), rotate_data (false), train_thresholds (false) +{ +} + + +void IndexLSH::set_typename() +{ + std::stringstream s; + s << "LSH_" << nbits << (rotate_data ? "r" : ""); + index_typename = s.str(); +} + +const float * IndexLSH::apply_preprocess (idx_t n, const float *x) const +{ + + float *xt = nullptr; + if (rotate_data) { + // also applies bias if exists + xt = rrot.apply (n, x); + } else if (d != nbits) { + xt = new float [nbits * n]; + float *xp = xt; + for (idx_t i = 0; i < n; i++) { + const float *xl = x + i * d; + for (int j = 0; j < nbits; j++) + *xp++ = xl [j]; + } + } + + if (train_thresholds) { + + if (xt == NULL) { + xt = new float [nbits * n]; + memcpy (xt, x, sizeof(*x) * n * nbits); + } + + float *xp = xt; + for (idx_t i = 0; i < n; i++) + for (int j = 0; j < nbits; j++) + *xp++ -= thresholds [j]; + } + + return xt ? xt : x; +} + + + +void IndexLSH::train (idx_t n, const float *x) +{ + if (train_thresholds) { + thresholds.resize (nbits); + train_thresholds = false; + const float *xt = apply_preprocess (n, x); + train_thresholds = true; + + float * transposed_x = new float [n * nbits]; + + for (idx_t i = 0; i < n; i++) + for (idx_t j = 0; j < nbits; j++) + transposed_x [j * n + i] = xt [i * nbits + j]; + if (xt != x) delete [] xt; + + for (idx_t i = 0; i < nbits; i++) { + float *xi = transposed_x + i * n; + // std::nth_element + std::sort (xi, xi + n); + if (n % 2 == 1) + thresholds [i] = xi [n / 2]; + else + thresholds [i] = (xi [n / 2 - 1] + xi [n / 2]) / 2; + + } + } + is_trained = true; +} + + +void IndexLSH::add (idx_t n, const float *x) +{ + FAISS_ASSERT (is_trained); + const float *xt = apply_preprocess (n, x); + + codes.resize ((ntotal + n) * bytes_per_vec); + fvecs2bitvecs (xt, &codes[ntotal * bytes_per_vec], nbits, n); + if (x != xt) + delete [] xt; + ntotal += n; +} + + +void IndexLSH::search ( + idx_t n, + const float *x, + idx_t k, + float *distances, + idx_t *labels) const +{ + FAISS_ASSERT (is_trained); + const float *xt = apply_preprocess (n, x); + + uint8_t * qcodes = new uint8_t [n * bytes_per_vec]; + fvecs2bitvecs (xt, qcodes, nbits, n); + + if (x != xt) + delete [] xt; + + int * idistances = new int [n * k]; + int_maxheap_array_t res = { size_t(n), size_t(k), labels, idistances}; + + hammings_knn (&res, qcodes, codes.data(), + ntotal, bytes_per_vec, true); + + delete [] qcodes; + + // convert distances to floats + for (int i = 0; i < k * n; i++) + distances[i] = idistances[i]; + delete [] idistances; + +} + + +void IndexLSH::transfer_thresholds (LinearTransform *vt) { + if (!train_thresholds) return; + FAISS_ASSERT (nbits == vt->d_out); + if (!vt->have_bias) { + vt->b.resize (nbits, 0); + vt->have_bias = true; + } + for (int i = 0; i < nbits; i++) + vt->b[i] -= thresholds[i]; + train_thresholds = false; + thresholds.clear(); +} + +void IndexLSH::reset() { + codes.clear(); + ntotal = 0; +} + + + +} // namespace faiss diff --git a/IndexLSH.h b/IndexLSH.h new file mode 100644 index 000000000..480e4def9 --- /dev/null +++ b/IndexLSH.h @@ -0,0 +1,86 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved. +// -*- c++ -*- + +#ifndef INDEX_LSH_H +#define INDEX_LSH_H + +#include + +#include "Index.h" +#include "VectorTransform.h" + +namespace faiss { + + +/** The sign of each vector component is put in a binary signature */ +struct IndexLSH:Index { + typedef unsigned char uint8_t; + + int nbits; ///< nb of bits per vector + int bytes_per_vec; ///< nb of 8-bits per encoded vector + bool rotate_data; ///< whether to apply a random rotation to input + bool train_thresholds; ///< whether we train thresholds or use 0 + + RandomRotationMatrix rrot; ///< optional random rotation + + std::vector thresholds; ///< thresholds to compare with + + /// encoded dataset + std::vector codes; + + IndexLSH ( + idx_t d, int nbits, + bool rotate_data = true, + bool train_thresholds = false); + + /** Preprocesses and resizes the input to the size required to + * binarize the data + * + * @param x input vectors, size n * d + * @return output vectors, size n * bits. May be the same pointer + * as x, otherwise it should be deleted by the caller + */ + const float *apply_preprocess (idx_t n, const float *x) const; + + virtual void set_typename () override; + + virtual void train (idx_t n, const float *x) override; + + virtual void add (idx_t n, const float *x) override; + + virtual void search ( + idx_t n, + const float *x, idx_t k, + float *distances, + idx_t *labels) const override; + + virtual void reset() override; + + /// transfer the thresholds to a pre-processing stage (and unset + /// train_thresholds) + void transfer_thresholds (LinearTransform * vt); + + virtual ~IndexLSH () {} + + IndexLSH (); +}; + + + +} + + + + + + +#endif diff --git a/IndexPQ.cpp b/IndexPQ.cpp new file mode 100644 index 000000000..895e3460e --- /dev/null +++ b/IndexPQ.cpp @@ -0,0 +1,886 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +/* Copyright 2004-present Facebook. All Rights Reserved. + Index based on product quantiztion. +*/ + +#include "IndexPQ.h" + + +#include +#include +#include + +#include + +#include "FaissAssert.h" +#include "hamming.h" + +namespace faiss { + +/********************************************************* + * IndexPQ implementation + ********************************************************/ + + +IndexPQ::IndexPQ (int d, size_t M, size_t nbits, MetricType metric): + Index(d, metric), pq(d, M, nbits) +{ + is_trained = false; + do_polysemous_training = false; + set_typename(); + polysemous_ht = nbits * M + 1; + search_type = ST_PQ; + encode_signs = false; +} + +IndexPQ::IndexPQ () +{ + metric_type = METRIC_L2; + is_trained = false; + do_polysemous_training = false; + set_typename(); + polysemous_ht = pq.nbits * pq.M + 1; + search_type = ST_PQ; + encode_signs = false; +} + + +void IndexPQ::set_typename () +{ + std::stringstream s; + s << "PQ_" << pq.M << "x" << pq.nbits; + index_typename = s.str(); +} + + +void IndexPQ::train (idx_t n, const float *x) +{ + if (!do_polysemous_training) { // standard training + pq.train(n, x); + } else { + idx_t ntrain_perm = polysemous_training.ntrain_permutation; + + if (ntrain_perm > n / 4) + ntrain_perm = n / 4; + if (verbose) { + printf ("PQ training on %ld points, remains %ld points: " + "training polysemous on %s\n", + n - ntrain_perm, ntrain_perm, + ntrain_perm == 0 ? "centroids" : "these"); + } + pq.train(n - ntrain_perm, x); + + polysemous_training.optimize_pq_for_hamming ( + pq, ntrain_perm, x + (n - ntrain_perm) * d); + } + is_trained = true; +} + + +void IndexPQ::add (idx_t n, const float *x) +{ + FAISS_ASSERT (is_trained); + codes.resize ((n + ntotal) * pq.code_size); + pq.compute_codes (x, &codes[ntotal * pq.code_size], n); + ntotal += n; +} + + + +void IndexPQ::reset() +{ + codes.clear(); + ntotal = 0; +} + +void IndexPQ::reconstruct_n (idx_t i0, idx_t ni, float *recons) const +{ + FAISS_ASSERT (ni == 0 || (i0 >= 0 && i0 + ni <= ntotal)); + for (idx_t i = 0; i < ni; i++) { + const uint8_t * code = &codes[(i0 + i) * pq.code_size]; + pq.decode (code, recons + i * d); + } +} + + +void IndexPQ::reconstruct (idx_t key, float * recons) const +{ + FAISS_ASSERT (key >= 0 && key < ntotal); + pq.decode (&codes[key * pq.code_size], recons); +} + + + + + + + +/***************************************** + * IndexPQ polysemous search routines + ******************************************/ + + + + + +void IndexPQ::search (idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const +{ + FAISS_ASSERT (is_trained); + if (search_type == ST_PQ) { // Simple PQ search + + if (metric_type == METRIC_L2) { + float_maxheap_array_t res = { + size_t(n), size_t(k), labels, distances }; + pq.search (x, n, codes.data(), ntotal, &res, true); + } else { + float_minheap_array_t res = { + size_t(n), size_t(k), labels, distances }; + pq.search_ip (x, n, codes.data(), ntotal, &res, true); + } + indexPQ_stats.nq += n; + indexPQ_stats.ncode += n * ntotal; + + } else if (search_type == ST_polysemous || + search_type == ST_polysemous_generalize) { + + FAISS_ASSERT (metric_type == METRIC_L2); + + search_core_polysemous (n, x, k, distances, labels); + + } else { // code-to-code distances + + uint8_t * q_codes = new uint8_t [n * pq.code_size]; + + if (!encode_signs) { + + pq.compute_codes (x, q_codes, n); + + + + } else { + FAISS_ASSERT (d == pq.nbits * pq.M); + memset (q_codes, 0, n * pq.code_size); + for (size_t i = 0; i < n; i++) { + const float *xi = x + i * d; + uint8_t *code = q_codes + i * pq.code_size; + for (int j = 0; j < d; j++) + if (xi[j] > 0) code [j>>3] |= 1 << (j & 7); + } + } + + if (search_type == ST_SDC) { + + float_maxheap_array_t res = { + size_t(n), size_t(k), labels, distances}; + + pq.search_sdc (q_codes, n, codes.data(), ntotal, &res, true); + + } else { + int * idistances = new int [n * k]; + + int_maxheap_array_t res = { + size_t (n), size_t (k), labels, idistances}; + + if (search_type == ST_HE) { + + hammings_knn (&res, q_codes, codes.data(), + ntotal, pq.code_size, true); + + } else if (search_type == ST_generalized_HE) { + + generalized_hammings_knn (&res, q_codes, codes.data(), + ntotal, pq.code_size, true); + } + + // convert distances to floats + for (int i = 0; i < k * n; i++) + distances[i] = idistances[i]; + delete [] idistances; + } + + delete [] q_codes; + indexPQ_stats.nq += n; + indexPQ_stats.ncode += n * ntotal; + } +} + + + + + +void IndexPQStats::reset() +{ + nq = ncode = n_hamming_pass = 0; +} + +IndexPQStats indexPQ_stats; + + +template +static size_t polysemous_inner_loop ( + const IndexPQ & index, + const float *dis_table_qi, const uint8_t *q_code, + size_t k, float *heap_dis, long *heap_ids) +{ + + int M = index.pq.M; + int code_size = index.pq.code_size; + int ksub = index.pq.ksub; + size_t ntotal = index.ntotal; + int ht = index.polysemous_ht; + + const uint8_t *b_code = index.codes.data(); + + size_t n_pass_i = 0; + + HammingComputer hc (q_code, code_size); + + for (long bi = 0; bi < ntotal; bi++) { + int hd = hc.hamming (b_code); + + if (hd < ht) { + n_pass_i ++; + + float dis = 0; + const float * dis_table = dis_table_qi; + for (int m = 0; m < M; m++) { + dis += dis_table [b_code[m]]; + dis_table += ksub; + } + + if (dis < heap_dis[0]) { + maxheap_pop (k, heap_dis, heap_ids); + maxheap_push (k, heap_dis, heap_ids, dis, bi); + } + } + b_code += code_size; + } + return n_pass_i; +} + + +void IndexPQ::search_core_polysemous (idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const +{ + FAISS_ASSERT (pq.code_size % 8 == 0); + FAISS_ASSERT (pq.byte_per_idx == 1); + + // PQ distance tables + float * dis_tables = new float [n * pq.ksub * pq.M]; + pq.compute_distance_tables (n, x, dis_tables); + + // Hamming embedding queries + uint8_t * q_codes = new uint8_t [n * pq.code_size]; + + if (false) { + pq.compute_codes (x, q_codes, n); + } else { +#pragma omp parallel for + for (idx_t qi = 0; qi < n; qi++) { + pq.compute_code_from_distance_table + (dis_tables + qi * pq.M * pq.ksub, + q_codes + qi * pq.code_size); + } + } + + size_t n_pass = 0; + +#pragma omp parallel for reduction (+: n_pass) + for (idx_t qi = 0; qi < n; qi++) { + const uint8_t * q_code = q_codes + qi * pq.code_size; + + const float * dis_table_qi = dis_tables + qi * pq.M * pq.ksub; + + long * heap_ids = labels + qi * k; + float *heap_dis = distances + qi * k; + maxheap_heapify (k, heap_dis, heap_ids); + + if (search_type == ST_polysemous) { + + switch (pq.code_size) { + case 4: + n_pass += polysemous_inner_loop + (*this, dis_table_qi, q_code, k, heap_dis, heap_ids); + break; + case 8: + n_pass += polysemous_inner_loop + (*this, dis_table_qi, q_code, k, heap_dis, heap_ids); + break; + case 16: + n_pass += polysemous_inner_loop + (*this, dis_table_qi, q_code, k, heap_dis, heap_ids); + break; + case 32: + n_pass += polysemous_inner_loop + (*this, dis_table_qi, q_code, k, heap_dis, heap_ids); + break; + case 20: + n_pass += polysemous_inner_loop + (*this, dis_table_qi, q_code, k, heap_dis, heap_ids); + break; + default: + if (pq.code_size % 8 == 0) + n_pass += polysemous_inner_loop + (*this, dis_table_qi, q_code, k, heap_dis, heap_ids); + else + n_pass += polysemous_inner_loop + (*this, dis_table_qi, q_code, k, heap_dis, heap_ids); + break; + } + } else { + switch (pq.code_size) { + case 8: + n_pass += polysemous_inner_loop + (*this, dis_table_qi, q_code, k, heap_dis, heap_ids); + break; + case 16: + n_pass += polysemous_inner_loop + (*this, dis_table_qi, q_code, k, heap_dis, heap_ids); + break; + case 32: + n_pass += polysemous_inner_loop + (*this, dis_table_qi, q_code, k, heap_dis, heap_ids); + break; + default: + n_pass += polysemous_inner_loop + (*this, dis_table_qi, q_code, k, heap_dis, heap_ids); + break; + } + } + maxheap_reorder (k, heap_dis, heap_ids); + } + + indexPQ_stats.nq += n; + indexPQ_stats.ncode += n * ntotal; + indexPQ_stats.n_hamming_pass += n_pass; + + delete [] q_codes; + delete [] dis_tables; + +} + + + + +/***************************************** + * Stats of IndexPQ codes + ******************************************/ + + + + +void IndexPQ::hamming_distance_table (idx_t n, const float *x, + int32_t *dis) const +{ + uint8_t * q_codes = new uint8_t [n * pq.code_size]; + + pq.compute_codes (x, q_codes, n); + + hammings (q_codes, codes.data(), n, ntotal, pq.code_size, dis); + + delete [] q_codes; +} + + +void IndexPQ::hamming_distance_histogram (idx_t n, const float *x, + idx_t nb, const float *xb, + long *hist) +{ + FAISS_ASSERT (metric_type == METRIC_L2); + FAISS_ASSERT (pq.code_size % 8 == 0); + FAISS_ASSERT (pq.byte_per_idx == 1); + + // Hamming embedding queries + uint8_t * q_codes = new uint8_t [n * pq.code_size]; + pq.compute_codes (x, q_codes, n); + + uint8_t * b_codes ; + + if (xb) { + b_codes = new uint8_t [nb * pq.code_size]; + pq.compute_codes (xb, b_codes, nb); + } else { + nb = ntotal; + b_codes = codes.data(); + } + int nbits = pq.M * pq.nbits; + memset (hist, 0, sizeof(*hist) * (nbits + 1)); + size_t bs = 256; + +#pragma omp parallel + { + std::vector histi (nbits + 1); + hamdis_t *distances = new hamdis_t [nb * bs]; +#pragma omp for + for (size_t q0 = 0; q0 < n; q0 += bs) { + // printf ("dis stats: %ld/%ld\n", q0, n); + size_t q1 = q0 + bs; + if (q1 > n) q1 = n; + + hammings (q_codes + q0 * pq.code_size, b_codes, + q1 - q0, nb, + pq.code_size, distances); + + for (size_t i = 0; i < nb * (q1 - q0); i++) + histi [distances [i]]++; + } +#pragma omp critical + { + for (int i = 0; i <= nbits; i++) + hist[i] += histi[i]; + } + delete [] distances; + } + + delete [] q_codes; + if (xb) + delete [] b_codes; + +} + + + + + + + + + + + + + + + + + + + + +/***************************************** + * MultiIndexQuantizer + ******************************************/ + + + +template +struct ArgSort { + const T * x; + bool operator() (size_t i, size_t j) { + return x[i] < x[j]; + } +}; + + +/** Array that maintains a permutation of its elements so that the + * array's elements are sorted + */ +template +struct SortedArray { + const T * x; + int N; + std::vector perm; + + explicit SortedArray (int N) { + this->N = N; + perm.resize (N); + } + + void init (const T*x) { + this->x = x; + for (int n = 0; n < N; n++) + perm[n] = n; + ArgSort cmp = {x }; + std::sort (perm.begin(), perm.end(), cmp); + } + + // get smallest value + T get_0 () { + return x[perm[0]]; + } + + // get delta between n-smallest and n-1 -smallest + T get_diff (int n) { + return x[perm[n]] - x[perm[n - 1]]; + } + + // remap orders counted from smallest to indices in array + int get_ord (int n) { + return perm[n]; + } +}; + + + +/** Array has n values. Sort the k first ones and copy the other ones + * into elements k..n-1 + */ +template +void partial_sort (int k, int n, + const typename C::T * vals, typename C::TI * perm) { + // insert first k elts in heap + for (int i = 1; i < k; i++) { + indirect_heap_push (i + 1, vals, perm, perm[i]); + } + + // insert next n - k elts in heap + for (int i = k; i < n; i++) { + typename C::TI id = perm[i]; + typename C::TI top = perm[0]; + + if (C::cmp(vals[top], vals[id])) { + indirect_heap_pop (k, vals, perm); + indirect_heap_push (k, vals, perm, id); + perm[i] = top; + } else { + // nothing, elt at i is good where it is. + } + } + + // order the k first elements in heap + for (int i = k - 1; i > 0; i--) { + typename C::TI top = perm[0]; + indirect_heap_pop (i + 1, vals, perm); + perm[i] = top; + } +} + +/** same as SortedArray, but only the k first elements are sorted */ +template +struct SemiSortedArray { + const T * x; + int N; + + // type of the heap: CMax = sort ascending + typedef CMax HC; + std::vector perm; + + int k; // k elements are sorted + + int initial_k, k_factor; + + explicit SemiSortedArray (int N) { + this->N = N; + perm.resize (N); + perm.resize (N); + initial_k = 3; + k_factor = 4; + } + + void init (const T*x) { + this->x = x; + for (int n = 0; n < N; n++) + perm[n] = n; + k = 0; + grow (initial_k); + } + + /// grow the sorted part of the array to size next_k + void grow (int next_k) { + if (next_k < N) { + partial_sort (next_k - k, N - k, x, &perm[k]); + k = next_k; + } else { // full sort of remainder of array + ArgSort cmp = {x }; + std::sort (perm.begin() + k, perm.end(), cmp); + k = N; + } + } + + // get smallest value + T get_0 () { + return x[perm[0]]; + } + + // get delta between n-smallest and n-1 -smallest + T get_diff (int n) { + if (n >= k) { + // want to keep powers of 2 - 1 + int next_k = (k + 1) * k_factor - 1; + grow (next_k); + } + return x[perm[n]] - x[perm[n - 1]]; + } + + // remap orders counted from smallest to indices in array + int get_ord (int n) { + assert (n < k); + return perm[n]; + } +}; + + + +/***************************************** + * Find the k smallest sums of M terms, where each term is taken in a + * table x of n values. + * + * A combination of terms is encoded as a scalar 0 <= t < n^M. The + * combination t0 ... t(M-1) that correspond to the sum + * + * sum = x[0, t0] + x[1, t1] + .... + x[M-1, t(M-1)] + * + * is encoded as + * + * t = t0 + t1 * n + t2 * n^2 + ... + t(M-1) * n^(M-1) + * + * MinSumK is an object rather than a function, so that storage can be + * re-used over several computations with the same sizes. use_seen is + * good when there may be ties in the x array and it is a concern if + * occasionally several t's are returned. + * + * @param x size M * n, values to add up + * @parms k nb of results to retrieve + * @param M nb of terms + * @param n nb of distinct values + * @param sums output, size k, sorted + * @prarm terms output, size k, with encoding as above + * + ******************************************/ +template +struct MinSumK { + int K; ///< nb of sums to return + int M; ///< nb of elements to sum up + int N; ///< nb of possible elements for each of the M terms + + /** the heap. + * We use a heap to maintain a queue of sums, with the associated + * terms involved in the sum. + */ + typedef CMin HC; + size_t heap_capacity, heap_size; + T *bh_val; + long *bh_ids; + + std::vector ssx; + std::vector weights; + + // all results get pushed several times. When there are ties, they + // are popped interleaved with others, so it is not easy to + // identify them. Therefore, this bit array just marks elements + // that were seen before. + std::vector seen; + + MinSumK (int K, int M, int N): K(K), M(M), N(N) { + heap_capacity = K * M; + // we'll do k steps, each step pushes at most M vals + bh_val = new T[heap_capacity]; + bh_ids = new long[heap_capacity]; + + weights.push_back (1); + for (int m = 1; m < M; m++) + weights.push_back(weights[m - 1] * N); + + if (use_seen) { + long n_ids = weights.back() * N; + seen.resize ((n_ids + 7) / 8); + } + + for (int m = 0; m < M; m++) + ssx.push_back (SSA(N)); + + } + + bool is_seen (long i) { + return (seen[i >> 3] >> (i & 7)) & 1; + } + + void mark_seen (long i) { + if (use_seen) + seen [i >> 3] |= 1 << (i & 7); + } + + void run (const T *x, T * sums, long * terms) { + heap_size = 0; + + for (int m = 0; m < M; m++) + ssx[m].init(x + N * m); + + { // intial result: take min for all elements + T sum = 0; + terms[0] = 0; + mark_seen (0); + for (int m = 0; m < M; m++) { + sum += ssx[m].get_0(); + } + sums[0] = sum; + for (int m = 0; m < M; m++) { + heap_push (++heap_size, bh_val, bh_ids, + sum + ssx[m].get_diff(1), + weights[m]); + } + } + + for (int k = 1; k < K; k++) { + // pop smallest value from heap + if (use_seen) {// skip already seen elements + while (is_seen (bh_ids[0])) { + assert (heap_size > 0); + heap_pop (heap_size--, bh_val, bh_ids); + } + } + assert (heap_size > 0); + + T sum = sums[k] = bh_val[0]; + long ti = terms[k] = bh_ids[0]; + + if (use_seen) { + mark_seen (ti); + heap_pop (heap_size--, bh_val, bh_ids); + } else { + do { + heap_pop (heap_size--, bh_val, bh_ids); + } while (heap_size > 0 && bh_ids[0] == ti); + } + + // enqueue followers + long ii = ti; + for (int m = 0; m < M; m++) { + long n = ii % N; + ii /= N; + if (n + 1 >= N) continue; + + enqueue_follower (ti, m, n, sum); + } + } + + /* + for (int k = 0; k < K; k++) + for (int l = k + 1; l < K; l++) + assert (terms[k] != terms[l]); + */ + + // convert indices by applying permutation + for (int k = 0; k < K; k++) { + long ii = terms[k]; + if (use_seen) { + // clear seen for reuse at next loop + seen[ii >> 3] = 0; + } + long ti = 0; + for (int m = 0; m < M; m++) { + long n = ii % N; + ti += weights[m] * ssx[m].get_ord(n); + ii /= N; + } + terms[k] = ti; + } + } + + + void enqueue_follower (long ti, int m, int n, T sum) { + T next_sum = sum + ssx[m].get_diff(n + 1); + long next_ti = ti + weights[m]; + heap_push (++heap_size, bh_val, bh_ids, next_sum, next_ti); + } + + + ~MinSumK () { + delete [] bh_ids; + delete [] bh_val; + } +}; + + + + +MultiIndexQuantizer::MultiIndexQuantizer (int d, + size_t M, + size_t nbits): + Index(d, METRIC_L2), pq(d, M, nbits) +{ + is_trained = false; + set_typename(); +} + + +void MultiIndexQuantizer::set_typename() +{ + std::stringstream s; + s << "MI_" << pq.M << "x" << pq.nbits; + index_typename = s.str(); +} + + +void MultiIndexQuantizer::train(idx_t n, const float *x) +{ + pq.train (n, x); + is_trained = true; + // count virtual elements in index + ntotal = 1; + for (int m = 0; m < pq.M; m++) + ntotal *= pq.ksub; +} + + +void MultiIndexQuantizer::search (idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const { + + float * dis_tables = new float [n * pq.ksub * pq.M]; + + pq.compute_distance_tables (n, x, dis_tables); + + /// TODO: special version for k==1 + +#pragma omp parallel + { + MinSumK , false> msk(k, pq.M, pq.ksub); +#pragma omp for + for (int i = 0; i < n; i++) { + msk.run (dis_tables + i * pq.ksub * pq.M, + distances + i * k, labels + i * k); + + } + } + delete [] dis_tables; +} + + +void MultiIndexQuantizer::reconstruct (idx_t key, float * recons) const +{ + if (pq.byte_per_idx == 1) { + uint8_t code[pq.M]; + long jj = key; + for (int m = 0; m < pq.M; m++) { + long n = jj % pq.ksub; + jj /= pq.ksub; + code[m] = n; + } + pq.decode (code, recons); + } else if (pq.byte_per_idx == 2) { + uint16_t code[pq.M]; + long jj = key; + for (int m = 0; m < pq.M; m++) { + long n = jj % pq.ksub; + jj /= pq.ksub; + code[m] = n; + } + pq.decode ((uint8_t*)code, recons); + } else FAISS_ASSERT(!"only 1 or 2 bytes per index supported"); +} + + +void MultiIndexQuantizer::add (idx_t n, const float *x) +{ + FAISS_ASSERT (!"This index has virtual elements, it does not support add"); +} + +void MultiIndexQuantizer::reset () +{ + FAISS_ASSERT (!"This index has virtual elements, " + "it does not support reset"); +} + + + + +} // END namespace faiss diff --git a/IndexPQ.h b/IndexPQ.h new file mode 100644 index 000000000..b19d27b95 --- /dev/null +++ b/IndexPQ.h @@ -0,0 +1,166 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved. +// -*- c++ -*- + +#ifndef FAISS_INDEX_PQ_H +#define FAISS_INDEX_PQ_H + +#include + +#include + +#include "Index.h" +#include "ProductQuantizer.h" +#include "PolysemousTraining.h" + +namespace faiss { + + +/** Index based on a product quantizer. Stored vectors are + * approximated by PQ codes. */ +struct IndexPQ: Index { + + /// The product quantizer used to encode the vectors + ProductQuantizer pq; + + /// Codes. Size ntotal * pq.code_size + std::vector codes; + + /** Constructor. + * + * @param d dimensionality of the input vectors + * @param M number of subquantizers + * @param nbits number of bit per subvector index + */ + IndexPQ (int d, ///< dimensionality of the input vectors + size_t M, ///< number of subquantizers + size_t nbits, ///< number of bit per subvector index + MetricType metric = METRIC_L2); + + IndexPQ (); + + virtual void set_typename () override; + + virtual void train (idx_t n, const float *x) override; + + virtual void add (idx_t n, const float *x) override; + + virtual void search ( + idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const override; + + virtual void reset() override; + + virtual void reconstruct_n (idx_t i0, idx_t ni, float *recons) + const override; + + virtual void reconstruct (idx_t key, float * recons) + const override; + + /****************************************************** + * Polysemous codes implementation + ******************************************************/ + bool do_polysemous_training; ///< false = standard PQ + + /// parameters used for the polysemous training + PolysemousTraining polysemous_training; + + /// how to perform the search in search_core + enum Search_type_t { + ST_PQ, ///< asymmetric product quantizer (default) + ST_HE, ///< Hamming distance on codes + ST_generalized_HE, ///< nb of same codes + ST_SDC, ///< symmetric product quantizer (SDC) + ST_polysemous, ///< HE filter (using ht) + PQ combination + ST_polysemous_generalize, ///< Filter on generalized Hamming + }; + + Search_type_t search_type; + + // just encode the sign of the components, instead of using the PQ encoder + // used only for the queries + bool encode_signs; + + /// Hamming threshold used for polysemy + int polysemous_ht; + + // actual polysemous search + void search_core_polysemous (idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const; + + /// prepare query for a polysemous search, but instead of + /// computing the result, just get the histogram of Hamming + /// distances. May be computed on a provided dataset if xb != NULL + /// @param dist_histogram (M * nbits + 1) + void hamming_distance_histogram (idx_t n, const float *x, + idx_t nb, const float *xb, + long *dist_histogram); + + /** compute pairwise distances between queries and database + * + * @param n nb of query vectors + * @param x query vector, size n * d + * @param dis output distances, size n * ntotal + */ + void hamming_distance_table (idx_t n, const float *x, + int32_t *dis) const; + +}; + + +/// statistics are robust to internal threading, but not if +/// IndexPQ::search is called by multiple threads +struct IndexPQStats { + size_t nq; // nb of queries run + size_t ncode; // nb of codes visited + + size_t n_hamming_pass; // nb of passed Hamming distance tests (for polysemy) + + IndexPQStats () {reset (); } + void reset (); +}; + +extern IndexPQStats indexPQ_stats; + + + +/** Quantizer where centroids are virtual: they are the Cartesian + * product of sub-centroids. */ +struct MultiIndexQuantizer: Index { + ProductQuantizer pq; + + MultiIndexQuantizer (int d, ///< dimension of the input vectors + size_t M, ///< number of subquantizers + size_t nbits); ///< number of bit per subvector index + + virtual void set_typename (); + + virtual void train (idx_t n, const float *x); + + + virtual void search (idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const; + + /// add and reset will crash at runtime + virtual void add (idx_t n, const float *x); + virtual void reset (); + + MultiIndexQuantizer () {} + + virtual void reconstruct (idx_t key, float * recons) const; +}; + + +} // namespace faiss + + + +#endif diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..768e2728b --- /dev/null +++ b/LICENSE @@ -0,0 +1,263 @@ + +Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International +Public License + +For Faiss + +Copyright (c) 2015-present, Facebook, Inc. All rights reserved. + + + By exercising the Licensed Rights (defined below), You accept and agree + to be bound by the terms and conditions of this Creative Commons + Attribution-NonCommercial-NoDerivatives 4.0 International Public + License ("Public License"). To the extent this Public License may be + interpreted as a contract, You are granted the Licensed Rights in + consideration of Your acceptance of these terms and conditions, and the + Licensor grants You such rights in consideration of benefits the + Licensor receives from making the Licensed Material available under + these terms and conditions. + + Section 1 - Definitions. + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material and + in which the Licensed Material is translated, altered, arranged, + transformed, or otherwise modified in a manner requiring permission + under the Copyright and Similar Rights held by the Licensor. For + purposes of this Public License, where the Licensed Material is a + musical work, performance, or sound recording, Adapted Material is + always produced where the Licensed Material is synched in timed + relation with a moving image. + b. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + c. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + d. Exceptions and Limitations means fair use, fair dealing, and/or any + other exception or limitation to Copyright and Similar Rights that + applies to Your use of the Licensed Material. + e. Licensed Material means the artistic or literary work, database, or + other material to which the Licensor applied this Public License. + f. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + g. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + h. NonCommercial means not primarily intended for or directed towards + commercial advantage or monetary compensation. For purposes of this + Public License, the exchange of the Licensed Material for other + material subject to Copyright and Similar Rights by digital + file-sharing or similar means is NonCommercial provided there is no + payment of monetary compensation in connection with the exchange. + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such as + reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + Section 2 - Scope. + a. License grant. + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + A. reproduce and Share the Licensed Material, in whole or in + part, for NonCommercial purposes only; and + B. produce and reproduce, but not Share, Adapted Material + for NonCommercial purposes only. + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with its + terms and conditions. + 3. Term. The term of this Public License is specified in Section + 6(a). + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in all + media and formats whether now known or hereafter created, and + to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including technical + modifications necessary to circumvent Effective Technological + Measures. For purposes of this Public License, simply making + modifications authorized by this Section 2(a)(4) never + produces Adapted Material. + 5. Downstream recipients. + A. Offer from the Licensor - Licensed Material. Every + recipient of the Licensed Material automatically receives + an offer from the Licensor to exercise the Licensed + Rights under the terms and conditions of this Public + License. + B. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + b. Other rights. + 1. Moral rights, such as the right of integrity, are not licensed + under this Public License, nor are publicity, privacy, and/or + other similar personality rights; however, to the extent + possible, the Licensor waives and/or agrees not to assert any + such rights held by the Licensor to the limited extent + necessary to allow You to exercise the Licensed Rights, but + not otherwise. + 2. Patent and trademark rights are not licensed under this Public + License. + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society under + any voluntary or waivable statutory or compulsory licensing + scheme. In all other cases the Licensor expressly reserves any + right to collect such royalties, including when the Licensed + Material is used other than for NonCommercial purposes. + + Section 3 - License Conditions. + + Your exercise of the Licensed Rights is expressly made subject to the + following conditions. + a. Attribution. + 1. If You Share the Licensed Material, You must: + A. retain the following if it is supplied by the Licensor + with the Licensed Material: + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if designated); + ii. a copyright notice; + iii. a notice that refers to this Public License; + iv. a notice that refers to the disclaimer of + warranties; + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + B. indicate if You modified the Licensed Material and retain + an indication of any previous modifications; and + C. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + For the avoidance of doubt, You do not have permission under + this Public License to Share Adapted Material. + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + Section 4 - Sui Generis Database Rights. + + Where the Licensed Rights include Sui Generis Database Rights that + apply to Your use of the Licensed Material: + a. for the avoidance of doubt, Section 2(a)(1) grants You the right to + extract, reuse, reproduce, and Share all or a substantial portion + of the contents of the database for NonCommercial purposes only and + provided You do not Share Adapted Material; + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + + For the avoidance of doubt, this Section 4 supplements and does not + replace Your obligations under this Public License where the Licensed + Rights include other Copyright and Similar Rights. + + Section 5 - Disclaimer of Warranties and Limitation of Liability. + a. Unless otherwise separately undertaken by the Licensor, to the + extent possible, the Licensor offers the Licensed Material as-is + and as-available, and makes no representations or warranties of any + kind concerning the Licensed Material, whether express, implied, + statutory, or other. This includes, without limitation, warranties + of title, merchantability, fitness for a particular purpose, + non-infringement, absence of latent or other defects, accuracy, or + the presence or absence of errors, whether or not known or + discoverable. Where disclaimers of warranties are not allowed in + full or in part, this disclaimer may not apply to You. + b. To the extent possible, in no event will the Licensor be liable to + You on any legal theory (including, without limitation, negligence) + or otherwise for any direct, special, indirect, incidental, + consequential, punitive, exemplary, or other losses, costs, + expenses, or damages arising out of this Public License or use of + the Licensed Material, even if the Licensor has been advised of the + possibility of such losses, costs, expenses, or damages. Where a + limitation of liability is not allowed in full or in part, this + limitation may not apply to You. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + Section 6 - Term and Termination. + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the violation; + or + 2. upon express reinstatement by the Licensor. + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations of + this Public License. + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + Section 7 - Other Terms and Conditions. + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + Section 8 - Interpretation. + a. For the avoidance of doubt, this Public License does not, and shall + not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + d. Nothing in this Public License constitutes or may be interpreted as + a limitation upon, or waiver of, any privileges and immunities that + apply to the Licensor or You, including from the legal processes of + any jurisdiction or authority. + diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..cdd13fffb --- /dev/null +++ b/Makefile @@ -0,0 +1,147 @@ + +# Copyright (c) 2015-present, Facebook, Inc. +# All rights reserved. +# +# This source code is licensed under the CC-by-NC license found in the +# LICENSE file in the root directory of this source tree. + +.SUFFIXES: .cpp .o + +MAKEFILE_INC=makefile.inc +-include $(MAKEFILE_INC) + +LIBNAME=libfaiss + +all: .env_ok $(LIBNAME).a tests/demo_ivfpq_indexing +py: _swigfaiss.so + + +############################# +# Various + + +LIBOBJ=hamming.o utils.o \ + IndexFlat.o IndexIVF.o IndexLSH.o IndexPQ.o \ + IndexIVFPQ.o \ + Clustering.o Heap.o VectorTransform.o index_io.o \ + PolysemousTraining.o MetaIndexes.o Index.o \ + ProductQuantizer.o AutoTune.o AuxIndexStructures.o + + +$(LIBNAME).a: $(LIBOBJ) + ar r $(LIBNAME).a $^ + +$(LIBNAME).$(SHAREDEXT): $(LIBOBJ) + $(CC) $(LDFLAGS) $(FAISSSHAREDFLAGS) -o $(LIBNAME).$(SHAREDEXT) $^ $(BLASLDFLAGS) + +.cpp.o: + $(CC) $(CFLAGS) -c $< -o $@ $(FLAGS) $(EXTRAFLAGS) + +utils.o: EXTRAFLAGS=$(BLASCFLAGS) +VectorTransform.o: EXTRAFLAGS=$(BLASCFLAGS) +ProductQuantizer.o: EXTRAFLAGS=$(BLASCFLAGS) + +# for MKL, the flags when generating a dynamic lib are different from +# the ones when making an executable, but by default they are the same + +BLASLDFLAGSSO ?= $(BLASLDFLAGS) + + +############################# +# pure C++ test in the test directory + +tests/test_blas: tests/test_blas.cpp + $(CC) $(CFLAGS) $< -o $@ $(BLASLDFLAGS) $(BLASCFLAGS) + + +tests/demo_ivfpq_indexing: tests/demo_ivfpq_indexing.cpp $(LIBNAME).a + $(CC) -o $@ $(CFLAGS) $< $(LIBNAME).a $(LDFLAGS) $(BLASLDFLAGS) + +tests/demo_sift1M: tests/demo_sift1M.cpp $(LIBNAME).a + $(CC) -o $@ $(CFLAGS) $< $(LIBNAME).a $(LDFLAGS) $(BLASLDFLAGS) + + +############################# +# SWIG interfaces + +HFILES = IndexFlat.h Index.h IndexLSH.h IndexPQ.h IndexIVF.h \ + IndexIVFPQ.h VectorTransform.h index_io.h utils.h \ + PolysemousTraining.h Heap.h MetaIndexes.h AuxIndexStructures.h \ + Clustering.h hamming.h AutoTune.h + +# also silently generates python/swigfaiss.py +python/swigfaiss_wrap.cxx: swigfaiss.swig $(HFILES) + $(SWIGEXEC) -python -c++ -Doverride= -o $@ $< + + +# extension is .so even on the mac +python/_swigfaiss.so: python/swigfaiss_wrap.cxx $(LIBNAME).a + $(CC) -I. $(CFLAGS) $(LDFLAGS) $(PYTHONCFLAGS) $(SHAREDFLAGS) \ + -o $@ $^ $(BLASLDFLAGSSO) + +_swigfaiss.so: python/_swigfaiss.so + cp python/_swigfaiss.so python/swigfaiss.py . + +############################# +# Dependencies + +# for i in *.cpp ; do gcc -I.. -MM $i -msse4; done +AutoTune.o: AutoTune.cpp AutoTune.h Index.h FaissAssert.h utils.h Heap.h \ + IndexFlat.h VectorTransform.h IndexLSH.h IndexPQ.h ProductQuantizer.h \ + Clustering.h PolysemousTraining.h IndexIVF.h IndexIVFPQ.h MetaIndexes.h +AuxIndexStructures.o: AuxIndexStructures.cpp AuxIndexStructures.h Index.h +BinaryCode.o: BinaryCode.cpp BinaryCode.h VectorTransform.h Index.h \ + FaissAssert.h hamming.h Heap.h +Clustering.o: Clustering.cpp Clustering.h Index.h utils.h Heap.h \ + FaissAssert.h IndexFlat.h +hamming.o: hamming.cpp hamming.h Heap.h FaissAssert.h +Heap.o: Heap.cpp Heap.h +Index.o: Index.cpp IndexFlat.h Index.h FaissAssert.h +IndexFlat.o: IndexFlat.cpp IndexFlat.h Index.h utils.h Heap.h \ + FaissAssert.h +index_io.o: index_io.cpp index_io.h FaissAssert.h IndexFlat.h Index.h \ + VectorTransform.h IndexLSH.h IndexPQ.h ProductQuantizer.h Clustering.h \ + Heap.h PolysemousTraining.h IndexIVF.h IndexIVFPQ.h +IndexIVF.o: IndexIVF.cpp IndexIVF.h Index.h Clustering.h Heap.h utils.h \ + hamming.h FaissAssert.h IndexFlat.h AuxIndexStructures.h +IndexIVFPQ.o: IndexIVFPQ.cpp IndexIVFPQ.h IndexIVF.h Index.h Clustering.h \ + Heap.h IndexPQ.h ProductQuantizer.h PolysemousTraining.h utils.h \ + IndexFlat.h hamming.h FaissAssert.h AuxIndexStructures.h +IndexLSH.o: IndexLSH.cpp IndexLSH.h Index.h VectorTransform.h utils.h \ + Heap.h hamming.h FaissAssert.h +IndexNested.o: IndexNested.cpp IndexNested.h IndexIVF.h Index.h \ + Clustering.h Heap.h IndexIVFPQ.h IndexPQ.h ProductQuantizer.h \ + PolysemousTraining.h IndexFlat.h FaissAssert.h +IndexPQ.o: IndexPQ.cpp IndexPQ.h Index.h ProductQuantizer.h Clustering.h \ + Heap.h PolysemousTraining.h FaissAssert.h hamming.h + +MetaIndexes.o: MetaIndexes.cpp MetaIndexes.h Index.h FaissAssert.h Heap.h +PolysemousTraining.o: PolysemousTraining.cpp PolysemousTraining.h \ + ProductQuantizer.h Clustering.h Index.h Heap.h utils.h hamming.h \ + FaissAssert.h +ProductQuantizer.o: ProductQuantizer.cpp ProductQuantizer.h Clustering.h \ + Index.h Heap.h FaissAssert.h VectorTransform.h IndexFlat.h utils.h +utils.o: utils.cpp utils.h Heap.h AuxIndexStructures.h Index.h \ + FaissAssert.h +VectorTransform.o: VectorTransform.cpp VectorTransform.h Index.h utils.h \ + Heap.h FaissAssert.h IndexPQ.h ProductQuantizer.h Clustering.h \ + PolysemousTraining.h + +clean: + rm -f $(LIBNAME).a $(LIBNAME).$(SHAREDEXT)* *.o \ + lua/swigfaiss.so lua/swigfaiss_wrap.cxx \ + python/_swigfaiss.so python/swigfaiss_wrap.cxx \ + python/swigfaiss.py _swigfaiss.so swigfaiss.py + +.env_ok: +ifeq ($(wildcard $(MAKEFILE_INC)),) + $(error Cannot find $(MAKEFILE_INC). Did you forget to copy the relevant file from ./example_makefiles?) +endif +ifeq ($(shell command -v $(CC) 2>/dev/null),) + $(error Cannot find $(CC), please refer to $(CURDIR)/makefile.inc to set up your environment) +endif + +.swig_ok: .env_ok +ifeq ($(shell command -v $(SWIGEXEC) 2>/dev/null),) + $(error Cannot find $(SWIGEXEC), please refer to $(CURDIR)/makefile.inc to set up your environment) +endif diff --git a/MetaIndexes.cpp b/MetaIndexes.cpp new file mode 100644 index 000000000..67a66a616 --- /dev/null +++ b/MetaIndexes.cpp @@ -0,0 +1,625 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved +// -*- c++ -*- + +#include "MetaIndexes.h" + +#include + +#include + +#include "FaissAssert.h" +#include "Heap.h" + +namespace faiss { + +/***************************************************** + * IndexIDMap implementation + *******************************************************/ + +IndexIDMap::IndexIDMap (Index *index): + index (index), + own_fields (false) +{ + FAISS_ASSERT (index->ntotal == 0 || !"index must be empty on input"); + is_trained = index->is_trained; + metric_type = index->metric_type; + verbose = index->verbose; + d = index->d; + set_typename (); +} + +void IndexIDMap::add (idx_t, const float *) +{ + FAISS_ASSERT (!"add does not make sense with IndexIDMap, " + "use add_with_ids"); +} + + +void IndexIDMap::train (idx_t n, const float *x) +{ + index->train (n, x); + is_trained = index->is_trained; +} + +void IndexIDMap::reset () +{ + index->reset (); + ntotal = 0; +} + + +void IndexIDMap::add_with_ids (idx_t n, const float * x, const long *xids) +{ + index->add (n, x); + for (idx_t i = 0; i < n; i++) + id_map.push_back (xids[i]); + ntotal = index->ntotal; +} + + +void IndexIDMap::search (idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const +{ + index->search (n, x, k, distances, labels); + idx_t *li = labels; + for (idx_t i = 0; i < n * k; i++) { + li[i] = li[i] < 0 ? li[i] : id_map[li[i]]; + } +} + + + +IndexIDMap::~IndexIDMap () +{ + if (own_fields) delete index; +} + +void IndexIDMap::set_typename () +{ + index_typename = "IDMap[" + index->index_typename + "]"; +} + + +/***************************************************** + * IndexShards implementation + *******************************************************/ + +// subroutines +namespace { + + +typedef Index::idx_t idx_t; + + +template +struct Thread { + Job job; + pthread_t thread; + + Thread () {} + + explicit Thread (const Job & job): job(job) {} + + void start () { + pthread_create (&thread, nullptr, run, this); + } + + void wait () { + pthread_join (thread, nullptr); + } + + static void * run (void *arg) { + static_cast (arg)->job.run(); + return nullptr; + } + +}; + + +/// callback + thread management to train 1 shard +struct TrainJob { + IndexShards *index; // the relevant index + int no; // shard number + idx_t n; // train points + const float *x; + + void run () + { + if (index->verbose) + printf ("begin train shard %d on %ld points\n", no, n); + index->shard_indexes [no]->train(n, x); + if (index->verbose) + printf ("end train shard %d\n", no); + } + +}; + +struct AddJob { + IndexShards *index; // the relevant index + int no; // shard number + + idx_t n; + const float *x; + const idx_t *ids; + + void run () + { + if (index->verbose) + printf ("begin add shard %d on %ld points\n", no, n); + if (ids) + index->shard_indexes[no]->add_with_ids (n, x, ids); + else + index->shard_indexes[no]->add (n, x); + if (index->verbose) + printf ("end add shard %d on %ld points\n", no, n); + } +}; + + + +/// callback + thread management to query in 1 shard +struct QueryJob { + const IndexShards *index; // the relevant index + int no; // shard number + + // query params + idx_t n; + const float *x; + idx_t k; + float *distances; + idx_t *labels; + + + void run () + { + if (index->verbose) + printf ("begin query shard %d on %ld points\n", no, n); + index->shard_indexes [no]->search (n, x, k, + distances, labels); + if (index->verbose) + printf ("end query shard %d\n", no); + } + + +}; + + + + +// add translation to all valid labels +void translate_labels (long n, idx_t *labels, long translation) +{ + if (translation == 0) return; + for (long i = 0; i < n; i++) { + if(labels[i] < 0) return; + labels[i] += translation; + } +} + + +/** merge result tables from several shards. + * @param all_distances size nshard * n * k + * @param all_labels idem + * @param translartions label translations to apply, size nshard + */ +template +void merge_tables (long n, long k, long nshard, + float *distances, idx_t *labels, + const float *all_distances, + idx_t *all_labels, + const long *translations) +{ + long shard_stride = n * k; +#pragma omp parallel for + for (long i = 0; i < n; i++) { + float *D = distances + i * k; + idx_t *I = labels + i * k; + const float *Ds = all_distances + i * k; + idx_t *Is = all_labels + i * k; + translate_labels (k, Is, translations[0]); + heap_heapify(k, D, I, Ds, Is, k); + for (int s = 1; s < nshard; s++) { + Ds += shard_stride; + Is += shard_stride; + translate_labels (k, Is, translations[s]); + heap_addn (k, D, I, Ds, Is, k); + } + heap_reorder(k, D, I); + } +} + + +}; + + + + +IndexShards::IndexShards (idx_t d, bool threaded, bool successive_ids): + Index (d), own_fields (false), + threaded (threaded), successive_ids (successive_ids) +{ + +} + +void IndexShards::add_shard (Index *idx) +{ + shard_indexes.push_back (idx); + sync_with_shard_indexes (); +} + +void IndexShards::sync_with_shard_indexes () +{ + if (shard_indexes.empty()) return; + Index * index0 = shard_indexes[0]; + d = index0->d; + metric_type = index0->metric_type; + is_trained = index0->is_trained; + ntotal = index0->ntotal; + for (int i = 1; i < shard_indexes.size(); i++) { + Index * index = shard_indexes[i]; + FAISS_ASSERT (metric_type == index->metric_type); + FAISS_ASSERT (d == index->d); + ntotal += index->ntotal; + } +} + + +void IndexShards::train (idx_t n, const float *x) +{ + + // pre-alloc because we don't want reallocs + std::vector > tss (shard_indexes.size()); + int nt = 0; + for (int i = 0; i < shard_indexes.size(); i++) { + if(!shard_indexes[i]->is_trained) { + TrainJob ts = {this, i, n, x}; + if (threaded) { + tss[nt] = Thread (ts); + tss[nt++].start(); + } else { + ts.run(); + } + } + } + for (int i = 0; i < nt; i++) { + tss[i].wait(); + } + sync_with_shard_indexes (); +} + +void IndexShards::add (idx_t n, const float *x) +{ + add_with_ids (n, x, nullptr); +} + + /** + * Cases (successive_ids, xids): + * - true, non-NULL ERROR: it makes no sense to pass in ids and + * request them to be shifted + * - true, NULL OK, but should be called only once (calls add() + * on sub-indexes). + * - false, non-NULL OK: will call add_with_ids with passed in xids + * distributed evenly over shards + * - false, NULL OK: will call add_with_ids on each sub-index, + * starting at ntotal + */ + +void IndexShards::add_with_ids (idx_t n, const float * x, const long *xids) +{ + + FAISS_ASSERT(!(successive_ids && xids) || + !"It makes no sense to pass in ids and request them to be shifted"); + + if (successive_ids) { + FAISS_ASSERT(!xids || + !"It makes no sense to pass in ids and request them to be shifted"); + FAISS_ASSERT(ntotal == 0 || + !"when adding to IndexShards with sucessive_ids, only add() " + "in a single pass is supported"); + } + + long nshard = shard_indexes.size(); + const long *ids = xids; + if (!ids && !successive_ids) { + long *aids = new long[n]; + for (long i = 0; i < n; i++) + aids[i] = ntotal + i; + ids = aids; + } + + std::vector > asa (shard_indexes.size()); + int nt = 0; + for (int i = 0; i < nshard; i++) { + long i0 = i * n / nshard; + long i1 = (i + 1) * n / nshard; + + AddJob as = {this, i, + i1 - i0, x + i0 * d, + ids ? ids + i0 : nullptr}; + if (threaded) { + asa[nt] = Thread(as); + asa[nt++].start(); + } else { + as.run(); + } + } + for (int i = 0; i < nt; i++) { + asa[i].wait(); + } + if (ids != xids) delete [] ids; + ntotal += n; +} + + +void IndexShards::reset () +{ + for (int i = 0; i < shard_indexes.size(); i++) { + shard_indexes[i]->reset (); + } + sync_with_shard_indexes (); +} + +void IndexShards::search ( + idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const +{ + long nshard = shard_indexes.size(); + float *all_distances = new float [nshard * k * n]; + idx_t *all_labels = new idx_t [nshard * k * n]; + +#if 1 + + // pre-alloc because we don't want reallocs + std::vector > qss (nshard); + for (int i = 0; i < nshard; i++) { + QueryJob qs = { + this, i, n, x, k, + all_distances + i * k * n, + all_labels + i * k * n + }; + if (threaded) { + qss[i] = Thread (qs); + qss[i].start(); + } else { + qs.run(); + } + } + + if (threaded) { + for (int i = 0; i < qss.size(); i++) { + qss[i].wait(); + } + } +#else + + // pre-alloc because we don't want reallocs + std::vector qss (nshard); + for (int i = 0; i < nshard; i++) { + QueryJob qs = { + this, i, n, x, k, + all_distances + i * k * n, + all_labels + i * k * n + }; + if (threaded) { + qss[i] = qs; + } else { + qs.run(); + } + } + + if (threaded) { +#pragma omp parallel for + for (int i = 0; i < qss.size(); i++) { + qss[i].run(); + } + } + +#endif + std::vector translations (nshard, 0); + if (successive_ids) { + translations[0] = 0; + for (int s = 0; s + 1 < nshard; s++) + translations [s + 1] = translations [s] + + shard_indexes [s]->ntotal; + } + + if (metric_type == METRIC_L2) { + merge_tables< CMax > ( + n, k, nshard, distances, labels, + all_distances, all_labels, translations.data ()); + } else { + merge_tables< CMin > ( + n, k, nshard, distances, labels, + all_distances, all_labels, translations.data ()); + } + + delete [] all_distances; + delete [] all_labels; +} + + +void IndexShards::set_typename () +{ + +} + +IndexShards::~IndexShards () +{ + if (own_fields) { + for (int s = 0; s < shard_indexes.size(); s++) + delete shard_indexes [s]; + } +} + + +/***************************************************** + * IndexSplitVectors implementation + *******************************************************/ + + +IndexSplitVectors::IndexSplitVectors (idx_t d, bool threaded): + Index (d), own_fields (false), + threaded (threaded), sum_d (0) +{ + +} + +void IndexSplitVectors::add_sub_index (Index *index) +{ + sub_indexes.push_back (index); + sync_with_sub_indexes (); +} + +void IndexSplitVectors::sync_with_sub_indexes () +{ + if (sub_indexes.empty()) return; + Index * index0 = sub_indexes[0]; + sum_d = index0->d; + metric_type = index0->metric_type; + is_trained = index0->is_trained; + ntotal = index0->ntotal; + for (int i = 1; i < sub_indexes.size(); i++) { + Index * index = sub_indexes[i]; + FAISS_ASSERT (metric_type == index->metric_type); + FAISS_ASSERT (ntotal == index->ntotal); + sum_d += index->d; + } + +} + +void IndexSplitVectors::add (idx_t n, const float *x) +{ + FAISS_ASSERT (!"not implemented"); +} + +namespace { + +/// callback + thread management to query in 1 shard +struct SplitQueryJob { + const IndexSplitVectors *index; // the relevant index + int no; // shard number + + // query params + idx_t n; + const float *x; + idx_t k; + float *distances; + idx_t *labels; + + + void run () + { + if (index->verbose) + printf ("begin query shard %d on %ld points\n", no, n); + const Index * sub_index = index->sub_indexes[no]; + long sub_d = sub_index->d, d = index->d; + idx_t ofs = 0; + for (int i = 0; i < no; i++) ofs += index->sub_indexes[i]->d; + float *sub_x = new float [sub_d * n]; + for (idx_t i = 0; i < n; i++) + memcpy (sub_x + i * sub_d, x + ofs + i * d, sub_d * sizeof (sub_x)); + sub_index->search (n, sub_x, k, distances, labels); + delete [] sub_x; + if (index->verbose) + printf ("end query shard %d\n", no); + } + +}; + + + +} + + + +void IndexSplitVectors::search ( + idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const +{ + FAISS_ASSERT (k == 1 || !"search implemented only for k=1"); + FAISS_ASSERT (sum_d == d || !"not enough indexes compared to # dimensions"); + + long nshard = sub_indexes.size(); + float *all_distances = new float [nshard * k * n]; + idx_t *all_labels = new idx_t [nshard * k * n]; + + // pre-alloc because we don't want reallocs + std::vector > qss (nshard); + for (int i = 0; i < nshard; i++) { + SplitQueryJob qs = { + this, i, n, x, k, + i == 0 ? distances : all_distances + i * k * n, + i == 0 ? labels : all_labels + i * k * n + }; + if (threaded) { + qss[i] = Thread (qs); + qss[i].start(); + } else { + qs.run(); + } + } + + if (threaded) { + for (int i = 0; i < qss.size(); i++) { + qss[i].wait(); + } + } + + long factor = 1; + for (int i = 0; i < nshard; i++) { + if (i > 0) { // results of 0 are already in the table + const float *distances_i = all_distances + i * k * n; + const idx_t *labels_i = all_labels + i * k * n; + for (long j = 0; j < n; j++) { + if (labels[j] >= 0 && labels_i[j] >= 0) { + labels[j] += labels_i[j] * factor; + distances[j] += distances_i[j]; + } else { + labels[j] = -1; + distances[j] = 0.0 / 0.0; + } + } + } + factor *= sub_indexes[i]->ntotal; + } + delete [] all_labels; + delete [] all_distances; +} + + +void IndexSplitVectors::train (idx_t n, const float *x) +{ + FAISS_ASSERT (!"not implemented"); +} + +void IndexSplitVectors::reset () +{ + FAISS_ASSERT (!"not implemented"); +} + +void IndexSplitVectors::set_typename () +{} + +IndexSplitVectors::~IndexSplitVectors () +{ + if (own_fields) { + for (int s = 0; s < sub_indexes.size(); s++) + delete sub_indexes [s]; + } +} + + + + + + +}; // namespace faiss diff --git a/MetaIndexes.h b/MetaIndexes.h new file mode 100644 index 000000000..4d10123dd --- /dev/null +++ b/MetaIndexes.h @@ -0,0 +1,142 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved. +// -*- c++ -*- + +#ifndef META_INDEXES_H +#define META_INDEXES_H + + +#include + + +#include "Index.h" + + +namespace faiss { + +/** Index that translates search results to ids */ +struct IndexIDMap : Index { + Index * index; ///! the sub-index + bool own_fields; ///! whether pointers are deleted in destructo + std::vector id_map; + + explicit IndexIDMap (Index *index); + + /// Same as add_core, but stores xids instead of sequential ids + /// @param xids if non-null, ids to store for the vectors (size n) + virtual void add_with_ids (idx_t n, const float * x, const long *xids) + override; + + /// this will fail. Use add_with_ids + virtual void add (idx_t n, const float *x) override; + + virtual void search ( + idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const override; + + virtual void train (idx_t n, const float *x) override; + + virtual void reset () override; + + virtual void set_typename () override; + + virtual ~IndexIDMap (); + +}; + +/** Index that concatenates the results from several sub-indexes + * + */ +struct IndexShards : Index { + + std::vector shard_indexes; + bool own_fields; /// should the sub-indexes be deleted along with this? + bool threaded; + bool successive_ids; + + /** + * @param threaded do we use one thread per sub_index or do + * queries sequentially? + * @param successive_ids should we shift the returned ids by + * the size of each sub-index or return them + * as they are? + */ + explicit IndexShards (idx_t d, bool threaded = false, + bool successive_ids = true); + + void add_shard (Index *); + + // update metric_type and ntotal. Call if you changes something in + // the shard indexes. + void sync_with_shard_indexes (); + + Index *at(int i) {return shard_indexes[i]; } + + /// supported only for sub-indices that implement add_with_ids + virtual void add (idx_t n, const float *x) override; + + virtual void add_with_ids (idx_t n, const float * x, const long *xids) + override; + + + + virtual void search ( + idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const override; + + virtual void train (idx_t n, const float *x) override; + + virtual void reset () override; + + virtual void set_typename () override; + + virtual ~IndexShards (); + +}; + +/** splits input vectors in segments and assigns each segment to a sub-index + * used to distribute a MultiIndexQuantizer + */ + +struct IndexSplitVectors: Index { + bool own_fields; + bool threaded; + std::vector sub_indexes; + idx_t sum_d; /// sum of dimensions seen so far + + explicit IndexSplitVectors (idx_t d, bool threaded = false); + + void add_sub_index (Index *); + void sync_with_sub_indexes (); + + virtual void add (idx_t n, const float *x) override; + + + virtual void search ( + idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const override; + + virtual void train (idx_t n, const float *x) override; + + virtual void reset () override; + + virtual void set_typename () override; + + virtual ~IndexSplitVectors (); + +}; + + + +} + + +#endif diff --git a/PolysemousTraining.cpp b/PolysemousTraining.cpp new file mode 100644 index 000000000..c08839f2b --- /dev/null +++ b/PolysemousTraining.cpp @@ -0,0 +1,962 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "PolysemousTraining.h" + +#include +#include +#include + +#include + +#include "utils.h" +#include "hamming.h" + +#include "FaissAssert.h" + +/***************************************** + * Mixed PQ / Hamming + ******************************************/ + +namespace faiss { + +/**************************************************** + * Optimization code + ****************************************************/ + + +SimulatedAnnealingParameters::SimulatedAnnealingParameters () +{ + // set some reasonable defaults for the optimization + init_temperature = 0.7; + temperature_decay = pow (0.9, 1/500.); + // reduce by a factor 0.9 every 500 it + n_iter = 500000; + n_redo = 2; + seed = 123; + verbose = 0; + only_bit_flips = false; + init_random = false; +} + +// what would the cost update be if iw and jw were swapped? +// default implementation just computes both and computes the difference +double PermutationObjective::cost_update ( + const int *perm, int iw, int jw) const +{ + double orig_cost = compute_cost (perm); + + std::vector perm2 (n); + for (int i = 0; i < n; i++) + perm2[i] = perm[i]; + perm2[iw] = perm[jw]; + perm2[jw] = perm[iw]; + + double new_cost = compute_cost (perm2.data()); + return new_cost - orig_cost; +} + + + + +SimulatedAnnealingOptimizer::SimulatedAnnealingOptimizer ( + PermutationObjective *obj, + const SimulatedAnnealingParameters &p): + SimulatedAnnealingParameters (p), + obj (obj), + n(obj->n), + logfile (nullptr) +{ + rnd = new RandomGenerator (p.seed); + FAISS_ASSERT (n < 100000 && n >=0 ); +} + +SimulatedAnnealingOptimizer::~SimulatedAnnealingOptimizer () +{ + delete rnd; +} + +// run the optimization and return the best result in best_perm +double SimulatedAnnealingOptimizer::run_optimization (int * best_perm) +{ + double min_cost = 1e30; + + // just do a few runs of the annealing and keep the lowest output cost + for (int it = 0; it < n_redo; it++) { + std::vector perm(n); + for (int i = 0; i < n; i++) + perm[i] = i; + if (init_random) { + for (int i = 0; i < n; i++) { + int j = i + rnd->rand_int (n - i); + std::swap (perm[i], perm[j]); + } + } + float cost = optimize (perm.data()); + if (logfile) fprintf (logfile, "\n"); + if(verbose > 1) { + printf (" optimization run %d: cost=%g %s\n", + it, cost, cost < min_cost ? "keep" : ""); + } + if (cost < min_cost) { + memcpy (best_perm, perm.data(), sizeof(perm[0]) * n); + min_cost = cost; + } + } + return min_cost; +} + +// perform the optimization loop, starting from and modifying +// permutation in-place +double SimulatedAnnealingOptimizer::optimize (int *perm) +{ + double cost = init_cost = obj->compute_cost (perm); + int log2n = 0; + while (!(n <= (1 << log2n))) log2n++; + double temperature = init_temperature; + int n_swap = 0, n_hot = 0; + for (int it = 0; it < n_iter; it++) { + temperature = temperature * temperature_decay; + int iw, jw; + if (only_bit_flips) { + iw = rnd->rand_int (n); + jw = iw ^ (1 << rnd->rand_int (log2n)); + } else { + iw = rnd->rand_int (n); + jw = rnd->rand_int (n - 1); + if (jw == iw) jw++; + } + double delta_cost = obj->cost_update (perm, iw, jw); + if (delta_cost < 0 || rnd->rand_float () < temperature) { + std::swap (perm[iw], perm[jw]); + cost += delta_cost; + n_swap++; + if (delta_cost >= 0) n_hot++; + } + if (verbose > 2 || (verbose > 1 && it % 10000 == 0)) { + printf (" iteration %d cost %g temp %g n_swap %d " + "(%d hot) \r", + it, cost, temperature, n_swap, n_hot); + fflush(stdout); + } + if (logfile) { + fprintf (logfile, "%d %g %g %d %d\n", + it, cost, temperature, n_swap, n_hot); + } + } + if (verbose > 1) printf("\n"); + return cost; +} + + + + + +/**************************************************** + * Cost functions: ReproduceDistanceTable + ****************************************************/ + + + + + + +static inline int hamming_dis (uint64_t a, uint64_t b) +{ + return __builtin_popcountl (a ^ b); +} + +namespace { + +/// optimize permutation to reproduce a distance table with Hamming distances +struct ReproduceWithHammingObjective : PermutationObjective { + int nbits; + double dis_weight_factor; + + static double sqr (double x) { return x * x; } + + + // weihgting of distances: it is more important to reproduce small + // distances well + double dis_weight (double x) const + { + return exp (-dis_weight_factor * x); + } + + std::vector target_dis; // wanted distances (size n^2) + std::vector weights; // weights for each distance (size n^2) + + // cost = quadratic difference between actual distance and Hamming distance + virtual double compute_cost (const int *perm) const + { + double cost = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + double wanted = target_dis [i * n + j]; + double w = weights [i * n + j]; + double actual = hamming_dis (perm[i], perm[j]); + cost += w * sqr (wanted - actual); + } + } + return cost; + } + + + // what would the cost update be if iw and jw were swapped? + // computed in O(n) instead of O(n^2) for the full re-computation + double cost_update (const int *perm, int iw, int jw) const + { + double delta_cost = 0; + + for (int i = 0; i < n; i++) { + if (i == iw) { + for (int j = 0; j < n; j++) { + double wanted = target_dis [i * n + j], + w = weights [i * n + j]; + double actual = hamming_dis (perm[i], perm[j]); + delta_cost -= w * sqr (wanted - actual); + double new_actual = hamming_dis ( + perm[jw], + perm[j == iw ? jw : j == jw ? iw : j]); + delta_cost += w * sqr (wanted - new_actual); + } + } else if (i == jw) { + for (int j = 0; j < n; j++) { + double wanted = target_dis [i * n + j], + w = weights [i * n + j]; + double actual = hamming_dis (perm[i], perm[j]); + delta_cost -= w * sqr (wanted - actual); + double new_actual = hamming_dis ( + perm[iw], + perm[j == iw ? jw : j == jw ? iw : j]); + delta_cost += w * sqr (wanted - new_actual); + } + } else { + int j = iw; + { + double wanted = target_dis [i * n + j], + w = weights [i * n + j]; + double actual = hamming_dis (perm[i], perm[j]); + delta_cost -= w * sqr (wanted - actual); + double new_actual = hamming_dis (perm[i], perm[jw]); + delta_cost += w * sqr (wanted - new_actual); + } + j = jw; + { + double wanted = target_dis [i * n + j], + w = weights [i * n + j]; + double actual = hamming_dis (perm[i], perm[j]); + delta_cost -= w * sqr (wanted - actual); + double new_actual = hamming_dis (perm[i], perm[iw]); + delta_cost += w * sqr (wanted - new_actual); + } + } + } + + return delta_cost; + } + + + + ReproduceWithHammingObjective ( + int nbits, + const std::vector & dis_table, + double dis_weight_factor): + nbits (nbits), dis_weight_factor (dis_weight_factor) + { + n = 1 << nbits; + FAISS_ASSERT (dis_table.size() == n * n); + set_affine_target_dis (dis_table); + } + + void set_affine_target_dis (const std::vector & dis_table) + { + double sum = 0, sum2 = 0; + int n2 = n * n; + for (int i = 0; i < n2; i++) { + sum += dis_table [i]; + sum2 += dis_table [i] * dis_table [i]; + } + double mean = sum / n2; + double stddev = sqrt(sum2 / n2 - (sum / n2) * (sum / n2)); + + target_dis.resize (n2); + + for (int i = 0; i < n2; i++) { + // the mapping function + double td = (dis_table [i] - mean) / stddev * sqrt(nbits / 4) + + nbits / 2; + target_dis[i] = td; + // compute a weight + weights.push_back (dis_weight (td)); + } + + } + + virtual ~ReproduceWithHammingObjective () {} +}; + +} // anonymous namespace + +// weihgting of distances: it is more important to reproduce small +// distances well +double ReproduceDistancesObjective::dis_weight (double x) const +{ + return exp (-dis_weight_factor * x); +} + + +double ReproduceDistancesObjective::get_source_dis (int i, int j) const +{ + return source_dis [i * n + j]; +} + +// cost = quadratic difference between actual distance and Hamming distance +double ReproduceDistancesObjective::compute_cost (const int *perm) const +{ + double cost = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + double wanted = target_dis [i * n + j]; + double w = weights [i * n + j]; + double actual = get_source_dis (perm[i], perm[j]); + cost += w * sqr (wanted - actual); + } + } + return cost; +} + +// what would the cost update be if iw and jw were swapped? +// computed in O(n) instead of O(n^2) for the full re-computation +double ReproduceDistancesObjective::cost_update( + const int *perm, int iw, int jw) const +{ + double delta_cost = 0; + for (int i = 0; i < n; i++) { + if (i == iw) { + for (int j = 0; j < n; j++) { + double wanted = target_dis [i * n + j], + w = weights [i * n + j]; + double actual = get_source_dis (perm[i], perm[j]); + delta_cost -= w * sqr (wanted - actual); + double new_actual = get_source_dis ( + perm[jw], + perm[j == iw ? jw : j == jw ? iw : j]); + delta_cost += w * sqr (wanted - new_actual); + } + } else if (i == jw) { + for (int j = 0; j < n; j++) { + double wanted = target_dis [i * n + j], + w = weights [i * n + j]; + double actual = get_source_dis (perm[i], perm[j]); + delta_cost -= w * sqr (wanted - actual); + double new_actual = get_source_dis ( + perm[iw], + perm[j == iw ? jw : j == jw ? iw : j]); + delta_cost += w * sqr (wanted - new_actual); + } + } else { + int j = iw; + { + double wanted = target_dis [i * n + j], + w = weights [i * n + j]; + double actual = get_source_dis (perm[i], perm[j]); + delta_cost -= w * sqr (wanted - actual); + double new_actual = get_source_dis (perm[i], perm[jw]); + delta_cost += w * sqr (wanted - new_actual); + } + j = jw; + { + double wanted = target_dis [i * n + j], + w = weights [i * n + j]; + double actual = get_source_dis (perm[i], perm[j]); + delta_cost -= w * sqr (wanted - actual); + double new_actual = get_source_dis (perm[i], perm[iw]); + delta_cost += w * sqr (wanted - new_actual); + } + } + } + return delta_cost; +} + + + +ReproduceDistancesObjective::ReproduceDistancesObjective ( + int n, + const double *source_dis_in, + const double *target_dis_in, + double dis_weight_factor): + dis_weight_factor (dis_weight_factor), + target_dis (target_dis_in) +{ + this->n = n; + set_affine_target_dis (source_dis_in); +} + +void ReproduceDistancesObjective::compute_mean_stdev ( + const double *tab, size_t n2, + double *mean_out, double *stddev_out) +{ + double sum = 0, sum2 = 0; + for (int i = 0; i < n2; i++) { + sum += tab [i]; + sum2 += tab [i] * tab [i]; + } + double mean = sum / n2; + double stddev = sqrt(sum2 / n2 - (sum / n2) * (sum / n2)); + *mean_out = mean; + *stddev_out = stddev; +} + +void ReproduceDistancesObjective::set_affine_target_dis ( + const double *source_dis_in) +{ + int n2 = n * n; + + double mean_src, stddev_src; + compute_mean_stdev (source_dis_in, n2, &mean_src, &stddev_src); + + double mean_target, stddev_target; + compute_mean_stdev (target_dis, n2, &mean_target, &stddev_target); + + printf ("map mean %g std %g -> mean %g std %g\n", + mean_src, stddev_src, mean_target, stddev_target); + + source_dis.resize (n2); + weights.resize (n2); + + for (int i = 0; i < n2; i++) { + // the mapping function + source_dis[i] = (source_dis_in[i] - mean_src) / stddev_src + * stddev_target + mean_target; + + // compute a weight + weights [i] = dis_weight (target_dis[i]); + } + +} + +/**************************************************** + * Cost functions: RankingScore + ****************************************************/ + +/// Maintains a 3D table of elementary costs. +/// Accumulates elements based on Hamming distance comparisons +template +struct Score3Computer: PermutationObjective { + + int nc; + + // cost matrix of size nc * nc *nc + // n_gt (i,j,k) = count of d_gt(x, y-) < d_gt(x, y+) + // where x has PQ code i, y- PQ code j and y+ PQ code k + std::vector n_gt; + + + /// the cost is a triple loop on the nc * nc * nc matrix of entries. + /// + Taccu compute (const int * perm) const + { + Taccu accu = 0; + const Ttab *p = n_gt.data(); + for (int i = 0; i < nc; i++) { + int ip = perm [i]; + for (int j = 0; j < nc; j++) { + int jp = perm [j]; + for (int k = 0; k < nc; k++) { + int kp = perm [k]; + if (hamming_dis (ip, jp) < + hamming_dis (ip, kp)) { + accu += *p; // n_gt [ ( i * nc + j) * nc + k]; + } + p++; + } + } + } + return accu; + } + + + /** cost update if entries iw and jw of the permutation would be + * swapped. + * + * The computation is optimized by avoiding elements in the + * nc*nc*nc cube that are known not to change. For nc=256, this + * reduces the nb of cells to visit to about 6/256 th of the + * cells. Practical speedup is about 8x, and the code is quite + * complex :-/ + */ + Taccu compute_update (const int *perm, int iw, int jw) const + { + assert (iw != jw); + if (iw > jw) std::swap (iw, jw); + + Taccu accu = 0; + const Ttab * n_gt_i = n_gt.data(); + for (int i = 0; i < nc; i++) { + int ip0 = perm [i]; + int ip = perm [i == iw ? jw : i == jw ? iw : i]; + + //accu += update_i (perm, iw, jw, ip0, ip, n_gt_i); + + accu += update_i_cross (perm, iw, jw, + ip0, ip, n_gt_i); + + if (ip != ip0) + accu += update_i_plane (perm, iw, jw, + ip0, ip, n_gt_i); + + n_gt_i += nc * nc; + } + + return accu; + } + + + Taccu update_i (const int *perm, int iw, int jw, + int ip0, int ip, const Ttab * n_gt_i) const + { + Taccu accu = 0; + const Ttab *n_gt_ij = n_gt_i; + for (int j = 0; j < nc; j++) { + int jp0 = perm[j]; + int jp = perm [j == iw ? jw : j == jw ? iw : j]; + for (int k = 0; k < nc; k++) { + int kp0 = perm [k]; + int kp = perm [k == iw ? jw : k == jw ? iw : k]; + int ng = n_gt_ij [k]; + if (hamming_dis (ip, jp) < hamming_dis (ip, kp)) { + accu += ng; + } + if (hamming_dis (ip0, jp0) < hamming_dis (ip0, kp0)) { + accu -= ng; + } + } + n_gt_ij += nc; + } + return accu; + } + + // 2 inner loops for the case ip0 != ip + Taccu update_i_plane (const int *perm, int iw, int jw, + int ip0, int ip, const Ttab * n_gt_i) const + { + Taccu accu = 0; + const Ttab *n_gt_ij = n_gt_i; + + for (int j = 0; j < nc; j++) { + if (j != iw && j != jw) { + int jp = perm[j]; + for (int k = 0; k < nc; k++) { + if (k != iw && k != jw) { + int kp = perm [k]; + Ttab ng = n_gt_ij [k]; + if (hamming_dis (ip, jp) < hamming_dis (ip, kp)) { + accu += ng; + } + if (hamming_dis (ip0, jp) < hamming_dis (ip0, kp)) { + accu -= ng; + } + } + } + } + n_gt_ij += nc; + } + return accu; + } + + /// used for the 8 cells were the 3 indices are swapped + inline Taccu update_k (const int *perm, int iw, int jw, + int ip0, int ip, int jp0, int jp, + int k, + const Ttab * n_gt_ij) const + { + Taccu accu = 0; + int kp0 = perm [k]; + int kp = perm [k == iw ? jw : k == jw ? iw : k]; + Ttab ng = n_gt_ij [k]; + if (hamming_dis (ip, jp) < hamming_dis (ip, kp)) { + accu += ng; + } + if (hamming_dis (ip0, jp0) < hamming_dis (ip0, kp0)) { + accu -= ng; + } + return accu; + } + + /// compute update on a line of k's, where i and j are swapped + Taccu update_j_line (const int *perm, int iw, int jw, + int ip0, int ip, int jp0, int jp, + const Ttab * n_gt_ij) const + { + Taccu accu = 0; + for (int k = 0; k < nc; k++) { + if (k == iw || k == jw) continue; + int kp = perm [k]; + Ttab ng = n_gt_ij [k]; + if (hamming_dis (ip, jp) < hamming_dis (ip, kp)) { + accu += ng; + } + if (hamming_dis (ip0, jp0) < hamming_dis (ip0, kp)) { + accu -= ng; + } + } + return accu; + } + + + /// considers the 2 pairs of crossing lines j=iw or jw and k = iw or kw + Taccu update_i_cross (const int *perm, int iw, int jw, + int ip0, int ip, const Ttab * n_gt_i) const + { + Taccu accu = 0; + const Ttab *n_gt_ij = n_gt_i; + + for (int j = 0; j < nc; j++) { + int jp0 = perm[j]; + int jp = perm [j == iw ? jw : j == jw ? iw : j]; + + accu += update_k (perm, iw, jw, ip0, ip, jp0, jp, iw, n_gt_ij); + accu += update_k (perm, iw, jw, ip0, ip, jp0, jp, jw, n_gt_ij); + + if (jp != jp0) + accu += update_j_line (perm, iw, jw, ip0, ip, jp0, jp, n_gt_ij); + + n_gt_ij += nc; + } + return accu; + } + + + /// PermutationObjective implementeation (just negates the scores + /// for minimization) + + virtual double compute_cost (const int *perm) const { + return -compute (perm); + } + + + + virtual double cost_update (const int *perm, int iw, int jw) const + { + double ret = -compute_update (perm, iw, jw); + return ret; + } + + virtual ~Score3Computer () {} + +}; + + + + + +struct IndirectSort { + const float *tab; + bool operator () (int a, int b) {return tab[a] < tab[b]; } +}; + + + +struct RankingScore2: Score3Computer { + int nbits; + int nq, nb; + const uint32_t *qcodes, *bcodes; + const float *gt_distances; + + RankingScore2 (int nbits, int nq, int nb, + const uint32_t *qcodes, const uint32_t *bcodes, + const float *gt_distances): + nbits(nbits), nq(nq), nb(nb), qcodes(qcodes), + bcodes(bcodes), gt_distances(gt_distances) + { + n = nc = 1 << nbits; + n_gt.resize (nc * nc * nc); + init_n_gt (); + } + + + double rank_weight (int r) + { + return 1.0 / (r + 1); + } + + /// count nb of i, j in a x b st. i < j + /// a and b should be sorted on input + /// they are the ranks of j and k respectively. + /// specific version for diff-of-rank weighting, cannot optimized + /// with a cumulative table + double accum_gt_weight_diff (const std::vector & a, + const std::vector & b) + { + int nb = b.size(), na = a.size(); + + double accu = 0; + int j = 0; + for (int i = 0; i < na; i++) { + int ai = a[i]; + while (j < nb && ai >= b[j]) j++; + + double accu_i = 0; + for (int k = j; k < b.size(); k++) + accu_i += rank_weight (b[k] - ai); + + accu += rank_weight (ai) * accu_i; + + } + return accu; + } + + void init_n_gt () + { + for (int q = 0; q < nq; q++) { + const float *gtd = gt_distances + q * nb; + const uint32_t *cb = bcodes;// all same codes + float * n_gt_q = & n_gt [qcodes[q] * nc * nc]; + + printf("init gt for q=%d/%d \r", q, nq); fflush(stdout); + + std::vector rankv (nb); + int * ranks = rankv.data(); + + // elements in each code bin, ordered by rank within each bin + std::vector > tab (nc); + + { // build rank table + IndirectSort s = {gtd}; + for (int j = 0; j < nb; j++) ranks[j] = j; + std::sort (ranks, ranks + nb, s); + } + + for (int rank = 0; rank < nb; rank++) { + int i = ranks [rank]; + tab [cb[i]].push_back (rank); + } + + + // this is very expensive. Any suggestion for improvement + // welcome. + for (int i = 0; i < nc; i++) { + std::vector & di = tab[i]; + for (int j = 0; j < nc; j++) { + std::vector & dj = tab[j]; + n_gt_q [i * nc + j] += accum_gt_weight_diff (di, dj); + + } + } + + } + + } + +}; + + +/***************************************** + * PolysemousTraining + ******************************************/ + + + +PolysemousTraining::PolysemousTraining () +{ + optimization_type = OT_ReproduceDistances_affine; + ntrain_permutation = 0; + dis_weight_factor = log(2); +} + + + +void PolysemousTraining::optimize_reproduce_distances ( + ProductQuantizer &pq) const +{ + + int dsub = pq.dsub; + + int n = pq.ksub; + int nbits = pq.nbits; + +#pragma omp parallel for + for (int m = 0; m < pq.M; m++) { + std::vector dis_table; + + // printf ("Optimizing quantizer %d\n", m); + + float * centroids = pq.get_centroids (m, 0); + + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + dis_table.push_back (fvec_L2sqr (centroids + i * dsub, + centroids + j * dsub, + dsub)); + } + } + + std::vector perm (n); + ReproduceWithHammingObjective obj ( + nbits, dis_table, + dis_weight_factor); + + + SimulatedAnnealingOptimizer optim (&obj, *this); + + if (log_pattern.size()) { + char fname[256]; + snprintf (fname, 256, log_pattern.c_str(), m); + printf ("opening log file %s\n", fname); + optim.logfile = fopen (fname, "w"); + FAISS_ASSERT (optim.logfile || !"could not open logfile"); + } + double final_cost = optim.run_optimization (perm.data()); + + if (verbose > 0) { + printf ("SimulatedAnnealingOptimizer for m=%d: %g -> %g\n", + m, optim.init_cost, final_cost); + } + + if (log_pattern.size()) fclose (optim.logfile); + + std::vector centroids_copy; + for (int i = 0; i < dsub * n; i++) + centroids_copy.push_back (centroids[i]); + + for (int i = 0; i < n; i++) + memcpy (centroids + perm[i] * dsub, + centroids_copy.data() + i * dsub, + dsub * sizeof(centroids[0])); + + } + +} + + +void PolysemousTraining::optimize_ranking ( + ProductQuantizer &pq, size_t n, const float *x) const +{ + + int dsub = pq.dsub; + + int nbits = pq.nbits; + + std::vector all_codes (pq.code_size * n); + + pq.compute_codes (x, all_codes.data(), n); + + FAISS_ASSERT (pq.byte_per_idx == 1); + + if (n == 0) + pq.compute_sdc_table (); + +#pragma omp parallel for + for (int m = 0; m < pq.M; m++) { + size_t nq, nb; + std::vector codes; // query codes, then db codes + std::vector gt_distances; // nq * nb matrix of distances + + if (n > 0) { + std::vector xtrain (n * dsub); + for (int i = 0; i < n; i++) + memcpy (xtrain.data() + i * dsub, + x + i * pq.d + m * dsub, + sizeof(float) * dsub); + + codes.resize (n); + for (int i = 0; i < n; i++) + codes [i] = all_codes [i * pq.code_size + m]; + + nq = n / 4; nb = n - nq; + const float *xq = xtrain.data(); + const float *xb = xq + nq * dsub; + + gt_distances.resize (nq * nb); + + pairwise_L2sqr (dsub, + nq, xq, + nb, xb, + gt_distances.data()); + } else { + nq = nb = pq.ksub; + codes.resize (2 * nq); + for (int i = 0; i < nq; i++) + codes[i] = codes [i + nq] = i; + + gt_distances.resize (nq * nb); + + memcpy (gt_distances.data (), + pq.sdc_table.data () + m * nq * nb, + sizeof (float) * nq * nb); + } + + double t0 = getmillisecs (); + + PermutationObjective *obj = new RankingScore2 ( + nbits, nq, nb, + codes.data(), codes.data() + nq, + gt_distances.data ()); + if (verbose > 0) { + printf(" m=%d, nq=%ld, nb=%ld, intialize RankingScore " + "in %.3f ms\n", + m, nq, nb, getmillisecs () - t0); + } + + SimulatedAnnealingOptimizer optim (obj, *this); + + if (log_pattern.size()) { + char fname[256]; + snprintf (fname, 256, log_pattern.c_str(), m); + printf ("opening log file %s\n", fname); + optim.logfile = fopen (fname, "w"); + FAISS_ASSERT (optim.logfile || !"could not open logfile"); + } + + std::vector perm (pq.ksub); + + double final_cost = optim.run_optimization (perm.data()); + printf ("SimulatedAnnealingOptimizer for m=%d: %g -> %g\n", + m, optim.init_cost, final_cost); + + if (log_pattern.size()) fclose (optim.logfile); + + delete obj; + + float * centroids = pq.get_centroids (m, 0); + + std::vector centroids_copy; + for (int i = 0; i < dsub * pq.ksub; i++) + centroids_copy.push_back (centroids[i]); + + for (int i = 0; i < pq.ksub; i++) + memcpy (centroids + perm[i] * dsub, + centroids_copy.data() + i * dsub, + dsub * sizeof(centroids[0])); + + } + +} + + + +void PolysemousTraining::optimize_pq_for_hamming (ProductQuantizer &pq, + size_t n, const float *x) const +{ + if (optimization_type == OT_None) { + + } else if (optimization_type == OT_ReproduceDistances_affine) { + optimize_reproduce_distances (pq); + } else { + optimize_ranking (pq, n, x); + } + + pq.compute_sdc_table (); + +} + + + +} // namespace faiss diff --git a/PolysemousTraining.h b/PolysemousTraining.h new file mode 100644 index 000000000..5a9447b0d --- /dev/null +++ b/PolysemousTraining.h @@ -0,0 +1,165 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved. +// -*- c++ -*- + +#ifndef FAISS_POLYSEMOUS_TRAINING_INCLUDED +#define FAISS_POLYSEMOUS_TRAINING_INCLUDED + + +#include "ProductQuantizer.h" + + +namespace faiss { + + + + +/// parameters used for the simulated annealing method +struct SimulatedAnnealingParameters { + + // optimization parameters + double init_temperature; // init probaility of accepting a bad swap + double temperature_decay; // at each iteration the temp is multiplied by this + int n_iter; // nb of iterations + int n_redo; // nb of runs of the simulation + int seed; // random seed + int verbose; + bool only_bit_flips; // restrict permutation changes to bit flips + bool init_random; // intialize with a random permutation (not identity) + + // set reasonable defaults + SimulatedAnnealingParameters (); + +}; + + +/// abstract class for the loss function +struct PermutationObjective { + + int n; + + virtual double compute_cost (const int *perm) const = 0; + + // what would the cost update be if iw and jw were swapped? + // default implementation just computes both and computes the difference + virtual double cost_update (const int *perm, int iw, int jw) const; + + virtual ~PermutationObjective () {} +}; + + +struct ReproduceDistancesObjective : PermutationObjective { + + double dis_weight_factor; + + static double sqr (double x) { return x * x; } + + // weihgting of distances: it is more important to reproduce small + // distances well + double dis_weight (double x) const; + + std::vector source_dis; ///< "real" corrected distances (size n^2) + const double * target_dis; ///< wanted distances (size n^2) + std::vector weights; ///< weights for each distance (size n^2) + + double get_source_dis (int i, int j) const; + + // cost = quadratic difference between actual distance and Hamming distance + virtual double compute_cost (const int *perm) const override; + + // what would the cost update be if iw and jw were swapped? + // computed in O(n) instead of O(n^2) for the full re-computation + virtual double cost_update (const int *perm, int iw, int jw) const override; + + ReproduceDistancesObjective ( + int n, + const double *source_dis_in, + const double *target_dis_in, + double dis_weight_factor); + + static void compute_mean_stdev (const double *tab, size_t n2, + double *mean_out, double *stddev_out); + + void set_affine_target_dis (const double *source_dis_in); + + virtual ~ReproduceDistancesObjective () {} + +}; + +struct RandomGenerator; + +/// Simulated annealing optimization algorithm for permutations. + struct SimulatedAnnealingOptimizer: SimulatedAnnealingParameters { + + PermutationObjective *obj; + int n; ///< size of the permutation + FILE *logfile; /// logs values of the cost function + + SimulatedAnnealingOptimizer (PermutationObjective *obj, + const SimulatedAnnealingParameters &p); + RandomGenerator *rnd; + + /// remember intial cost of optimization + double init_cost; + + // main entry point. Perform the optimization loop, starting from + // and modifying permutation in-place + double optimize (int *perm); + + // run the optimization and return the best result in best_perm + double run_optimization (int * best_perm); + + virtual ~SimulatedAnnealingOptimizer (); +}; + + + + +/// optimizes the order of indices in a ProductQuantizer +struct PolysemousTraining: SimulatedAnnealingParameters { + + enum Optimization_type_t { + OT_None, + OT_ReproduceDistances_affine, ///< default + OT_Ranking_weighted_diff /// same as _2, but use rank of y+ - rank of y- + }; + Optimization_type_t optimization_type; + + // use 1/4 of the training points for the optimization, with + // max. ntrain_permutation. If ntrain_permutation == 0: train on + // centroids + int ntrain_permutation; + double dis_weight_factor; // decay of exp that weights distance loss + + // filename pattern for the logging of iterations + std::string log_pattern; + + // sets default values + PolysemousTraining (); + + /// reorder the centroids so that the Hamming distace becomes a + /// good approximation of the SDC distance (called by train) + void optimize_pq_for_hamming (ProductQuantizer & pq, + size_t n, const float *x) const; + + /// called by optimize_pq_for_hamming + void optimize_ranking (ProductQuantizer &pq, size_t n, const float *x) const; + /// called by optimize_pq_for_hamming + void optimize_reproduce_distances (ProductQuantizer &pq) const; + +}; + + + +} // namespace faiss + + +#endif diff --git a/ProductQuantizer.cpp b/ProductQuantizer.cpp new file mode 100644 index 000000000..030233798 --- /dev/null +++ b/ProductQuantizer.cpp @@ -0,0 +1,665 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +/* Copyright 2004-present Facebook. All Rights Reserved. + Index based on product quantiztion. +*/ + +#include "ProductQuantizer.h" + + +#include +#include +#include + +#include + +#include "FaissAssert.h" +#include "VectorTransform.h" +#include "IndexFlat.h" +#include "utils.h" + + +extern "C" { + +/* declare BLAS functions, see http://www.netlib.org/clapack/cblas/ */ + +int sgemm_ (const char *transa, const char *transb, FINTEGER *m, FINTEGER * + n, FINTEGER *k, const float *alpha, const float *a, + FINTEGER *lda, const float *b, FINTEGER * + ldb, float *beta, float *c, FINTEGER *ldc); + +} + + +namespace faiss { + + + + +/* compute an estimator using look-up tables for typical values of M */ +template +void pq_estimators_from_tables_Mmul4 (int M, const CT * codes, + size_t ncodes, + const float * __restrict dis_table, + size_t ksub, + size_t k, + float * heap_dis, + long * heap_ids) +{ + + for (size_t j = 0; j < ncodes; j++) { + float dis = 0; + const float *dt = dis_table; + + for (size_t m = 0; m < M; m+=4) { + float dism = 0; + dism = dt[*codes++]; dt += ksub; + dism += dt[*codes++]; dt += ksub; + dism += dt[*codes++]; dt += ksub; + dism += dt[*codes++]; dt += ksub; + dis += dism; + } + + if (C::cmp (heap_dis[0], dis)) { + heap_pop (k, heap_dis, heap_ids); + heap_push (k, heap_dis, heap_ids, dis, j); + } + } +} + + +template +void pq_estimators_from_tables_M4 (const CT * codes, + size_t ncodes, + const float * __restrict dis_table, + size_t ksub, + size_t k, + float * heap_dis, + long * heap_ids) +{ + + for (size_t j = 0; j < ncodes; j++) { + float dis = 0; + const float *dt = dis_table; + dis = dt[*codes++]; dt += ksub; + dis += dt[*codes++]; dt += ksub; + dis += dt[*codes++]; dt += ksub; + dis += dt[*codes++]; + + if (C::cmp (heap_dis[0], dis)) { + heap_pop (k, heap_dis, heap_ids); + heap_push (k, heap_dis, heap_ids, dis, j); + } + } +} + + +template +static inline void pq_estimators_from_tables (const ProductQuantizer * pq, + const CT * codes, + size_t ncodes, + const float * dis_table, + size_t k, + float * heap_dis, + long * heap_ids) +{ + + if (pq->M == 4) { + + pq_estimators_from_tables_M4 (codes, ncodes, + dis_table, pq->ksub, k, + heap_dis, heap_ids); + return; + } + + if (pq->M % 4 == 0) { + pq_estimators_from_tables_Mmul4 (pq->M, codes, ncodes, + dis_table, pq->ksub, k, + heap_dis, heap_ids); + return; + } + + /* Default is relatively slow */ + const size_t M = pq->M; + const size_t ksub = pq->ksub; + for (size_t j = 0; j < ncodes; j++) { + float dis = 0; + const float * __restrict dt = dis_table; + for (int m = 0; m < M; m++) { + dis += dt[*codes++]; + dt += ksub; + } + if (C::cmp (heap_dis[0], dis)) { + heap_pop (k, heap_dis, heap_ids); + heap_push (k, heap_dis, heap_ids, dis, j); + } + } +} + + +/********************************************* + * PQ implementation + *********************************************/ + + + +ProductQuantizer::ProductQuantizer (size_t d, size_t M, size_t nbits): + d(d), M(M), nbits(nbits) +{ + set_derived_values (); +} + +ProductQuantizer::ProductQuantizer (): + d(0), M(1), nbits(0) +{ + set_derived_values (); +} + + + +void ProductQuantizer::set_derived_values () { + // quite a few derived values + FAISS_ASSERT (d % M == 0); + dsub = d / M; + byte_per_idx = (nbits + 7) / 8; + code_size = byte_per_idx * M; + ksub = 1 << nbits; + centroids.resize (d * ksub); + verbose = false; + train_type = Train_default; +} + + +void ProductQuantizer::set_params (const float * centroids_, int m) +{ + memcpy (get_centroids(m, 0), centroids_, + ksub * dsub * sizeof (centroids_[0])); +} + + +static void init_hypercube (int d, int nbits, + int n, const float * x, + float *centroids) +{ + + std::vector mean (d); + for (int i = 0; i < n; i++) + for (int j = 0; j < d; j++) + mean [j] += x[i * d + j]; + + float maxm = 0; + for (int j = 0; j < d; j++) { + mean [j] /= n; + if (fabs(mean[j]) > maxm) maxm = fabs(mean[j]); + } + + for (int i = 0; i < (1 << nbits); i++) { + float * cent = centroids + i * d; + for (int j = 0; j < nbits; j++) + cent[j] = mean [j] + (((i >> j) & 1) ? 1 : -1) * maxm; + for (int j = nbits; j < d; j++) + cent[j] = mean [j]; + } + + +} + +static void init_hypercube_pca (int d, int nbits, + int n, const float * x, + float *centroids) +{ + PCAMatrix pca (d, nbits); + pca.train (n, x); + + + for (int i = 0; i < (1 << nbits); i++) { + float * cent = centroids + i * d; + for (int j = 0; j < d; j++) { + cent[j] = pca.mean[j]; + float f = 1.0; + for (int k = 0; k < nbits; k++) + cent[j] += f * + sqrt (pca.eigenvalues [k]) * + (((i >> k) & 1) ? 1 : -1) * + pca.PCAMat [j + k * d]; + } + } + +} + +void ProductQuantizer::train (int n, const float * x) +{ + if (train_type != Train_shared) { + train_type_t final_train_type; + final_train_type = train_type; + if (train_type == Train_hypercube || + train_type == Train_hypercube_pca) { + if (dsub < nbits) { + final_train_type = Train_default; + printf ("cannot train hypercube: nbits=%ld > log2(d=%ld)\n", + nbits, dsub); + } + } + + float * xslice = new float[n * dsub]; + for (int m = 0; m < M; m++) { + for (int j = 0; j < n; j++) + memcpy (xslice + j * dsub, + x + j * d + m * dsub, + dsub * sizeof(float)); + + Clustering clus (dsub, ksub, cp); + + // we have some initialization for the centroids + if (final_train_type != Train_default) { + clus.centroids.resize (dsub * ksub); + } + + switch (final_train_type) { + case Train_hypercube: + init_hypercube (dsub, nbits, n, xslice, + clus.centroids.data ()); + break; + case Train_hypercube_pca: + init_hypercube_pca (dsub, nbits, n, xslice, + clus.centroids.data ()); + break; + case Train_hot_start: + memcpy (clus.centroids.data(), + get_centroids (m, 0), + dsub * ksub * sizeof (float)); + break; + default: ; + } + + if(verbose) { + clus.verbose = true; + printf ("Training PQ slice %d/%zd\n", m, M); + } + IndexFlatL2 index (dsub); + clus.train (n, xslice, index); + set_params (clus.centroids.data(), m); + } + delete [] xslice; + + } else { + + Clustering clus (dsub, ksub, cp); + + if(verbose) { + clus.verbose = true; + printf ("Training all PQ slices at once\n"); + } + + IndexFlatL2 index (dsub); + clus.train (n * M, x, index); + for (int m = 0; m < M; m++) { + set_params (clus.centroids.data(), m); + } + + } +} + + +void ProductQuantizer::compute_code (const float * x, uint8_t * code) const +{ + float distances [ksub]; + for (size_t m = 0; m < M; m++) { + float mindis = 1e20; + int idxm = -1; + const float * xsub = x + m * dsub; + + fvec_L2sqr_ny (distances, xsub, get_centroids(m, 0), dsub, ksub); + + /* Find best centroid */ + size_t i; + for (i = 0; i < ksub; i++) { + float dis = distances [i]; + if (dis < mindis) { + mindis = dis; + idxm = i; + } + } + switch (byte_per_idx) { + case 1: code[m] = (uint8_t) idxm; break; + case 2: ((uint16_t *) code)[m] = (uint16_t) idxm; break; + } + } + +} + +void ProductQuantizer::decode (const uint8_t *code, float *x) const +{ + if (byte_per_idx == 1) { + for (size_t m = 0; m < M; m++) { + memcpy (x + m * dsub, get_centroids(m, code[m]), + sizeof(float) * dsub); + } + } else { + const uint16_t *c = (const uint16_t*) code; + for (size_t m = 0; m < M; m++) { + memcpy (x + m * dsub, get_centroids(m, c[m]), + sizeof(float) * dsub); + } + } +} + + +void ProductQuantizer::decode (const uint8_t *code, float *x, size_t n) const +{ + for (size_t i = 0; i < n; i++) { + this->decode (code + M * i, x + d * i); + } +} + + +void ProductQuantizer::compute_code_from_distance_table (const float *tab, + uint8_t *code) const +{ + for (size_t m = 0; m < M; m++) { + float mindis = 1e20; + int idxm = -1; + + /* Find best centroid */ + for (size_t j = 0; j < ksub; j++) { + float dis = *tab++; + if (dis < mindis) { + mindis = dis; + idxm = j; + } + } + switch (byte_per_idx) { + case 1: code[m] = (uint8_t) idxm; break; + case 2: ((uint16_t *) code)[m] = (uint16_t) idxm; break; + } + } +} + +void ProductQuantizer::compute_codes (const float * x, + uint8_t * codes, + size_t n) const +{ + if (dsub < 16) { // simple direct computation + +#pragma omp parallel for + for (size_t i = 0; i < n; i++) + compute_code (x + i * d, codes + i * code_size); + + } else { // worthwile to use BLAS + float *dis_tables = new float [n * ksub * M]; + compute_distance_tables (n, x, dis_tables); + +#pragma omp parallel for + for (size_t i = 0; i < n; i++) { + uint8_t * code = codes + i * code_size; + const float * tab = dis_tables + i * ksub * M; + compute_code_from_distance_table (tab, code); + } + delete [] dis_tables; + } +} + + +void ProductQuantizer::compute_distance_table (const float * x, + float * dis_table) const +{ + size_t m; + + for (m = 0; m < M; m++) { + fvec_L2sqr_ny (dis_table + m * ksub, + x + m * dsub, + get_centroids(m, 0), + dsub, + ksub); + } +} + +void ProductQuantizer::compute_inner_prod_table (const float * x, + float * dis_table) const +{ + size_t m; + + for (m = 0; m < M; m++) { + fvec_inner_products_ny (dis_table + m * ksub, + x + m * dsub, + get_centroids(m, 0), + dsub, + ksub); + } +} + + +void ProductQuantizer::compute_distance_tables ( + size_t nx, + const float * x, + float * dis_tables) const +{ + + if (dsub < 16) { + +#pragma omp parallel for + for (size_t i = 0; i < nx; i++) { + compute_distance_table (x + i * d, dis_tables + i * ksub * M); + } + + } else { // use BLAS + + for (int m = 0; m < M; m++) { + pairwise_L2sqr (dsub, + nx, x + dsub * m, + ksub, centroids.data() + m * dsub * ksub, + dis_tables + ksub * m, + d, dsub, ksub * M); + } + } +} + +void ProductQuantizer::compute_inner_prod_tables ( + size_t nx, + const float * x, + float * dis_tables) const +{ + + if (dsub < 16) { + +#pragma omp parallel for + for (size_t i = 0; i < nx; i++) { + compute_inner_prod_table (x + i * d, dis_tables + i * ksub * M); + } + + } else { // use BLAS + + // compute distance tables + for (int m = 0; m < M; m++) { + FINTEGER ldc = ksub * M, nxi = nx, ksubi = ksub, + dsubi = dsub, di = d; + float one = 1.0, zero = 0; + + sgemm_ ("Transposed", "Not transposed", + &ksubi, &nxi, &dsubi, + &one, ¢roids [m * dsub * ksub], &dsubi, + x + dsub * m, &di, + &zero, dis_tables + ksub * m, &ldc); + } + + } +} + +template +static void pq_knn_search_with_tables ( + const ProductQuantizer * pq, + const float *dis_tables, + const uint8_t * codes, + const size_t ncodes, + HeapArray * res, + bool init_finalize_heap) +{ + size_t k = res->k, nx = res->nh; + size_t ksub = pq->ksub, M = pq->M; + + +#pragma omp parallel for + for (size_t i = 0; i < nx; i++) { + /* query preparation for asymmetric search: compute look-up tables */ + const float* dis_table = dis_tables + i * ksub * M; + + /* Compute distances and keep smallest values */ + long * __restrict heap_ids = res->ids + i * k; + float * __restrict heap_dis = res->val + i * k; + + if (init_finalize_heap) { + heap_heapify (k, heap_dis, heap_ids); + } + + pq_estimators_from_tables (pq, + (CT*)codes, ncodes, + dis_table, + k, heap_dis, heap_ids); + if (init_finalize_heap) { + heap_reorder (k, heap_dis, heap_ids); + } + } +} + + /* +static inline void pq_estimators_from_tables (const ProductQuantizer * pq, + const CT * codes, + size_t ncodes, + const float * dis_table, + size_t k, + float * heap_dis, + long * heap_ids) + */ +void ProductQuantizer::search (const float * __restrict x, + size_t nx, + const uint8_t * codes, + const size_t ncodes, + float_maxheap_array_t * res, + bool init_finalize_heap) const +{ + float * dis_tables = new float [nx * ksub * M]; + compute_distance_tables (nx, x, dis_tables); + FAISS_ASSERT(nx == res->nh); + + if (byte_per_idx == 1) { + + pq_knn_search_with_tables > ( + this, dis_tables, codes, ncodes, res, init_finalize_heap); + + } else if (byte_per_idx == 2) { + pq_knn_search_with_tables > ( + this, dis_tables, codes, ncodes, res, init_finalize_heap); + + } + delete [] dis_tables; +} + +void ProductQuantizer::search_ip (const float * __restrict x, + size_t nx, + const uint8_t * codes, + const size_t ncodes, + float_minheap_array_t * res, + bool init_finalize_heap) const +{ + float * dis_tables = new float [nx * ksub * M]; + compute_inner_prod_tables (nx, x, dis_tables); + FAISS_ASSERT(nx == res->nh); + + if (byte_per_idx == 1) { + + pq_knn_search_with_tables > ( + this, dis_tables, codes, ncodes, res, init_finalize_heap); + + } else if (byte_per_idx == 2) { + pq_knn_search_with_tables > ( + this, dis_tables, codes, ncodes, res, init_finalize_heap); + } + delete [] dis_tables; +} + + + +static float sqr (float x) { + return x * x; +} + +void ProductQuantizer::compute_sdc_table () +{ + sdc_table.resize (M * ksub * ksub); + + for (int m = 0; m < M; m++) { + + const float *cents = centroids.data() + m * ksub * dsub; + float * dis_tab = sdc_table.data() + m * ksub * ksub; + + // TODO optimize with BLAS + for (int i = 0; i < ksub; i++) { + const float *centi = cents + i * dsub; + for (int j = 0; j < ksub; j++) { + float accu = 0; + const float *centj = cents + j * dsub; + for (int k = 0; k < dsub; k++) + accu += sqr (centi[k] - centj[k]); + dis_tab [i + j * ksub] = accu; + } + } + } +} + +void ProductQuantizer::search_sdc (const uint8_t * qcodes, + size_t nq, + const uint8_t * bcodes, + const size_t nb, + float_maxheap_array_t * res, + bool init_finalize_heap) const +{ + FAISS_ASSERT (sdc_table.size() == M * ksub * ksub); + size_t k = res->k; + + FAISS_ASSERT (byte_per_idx == 1); + +#pragma omp parallel for + for (size_t i = 0; i < nq; i++) { + + /* Compute distances and keep smallest values */ + long * heap_ids = res->ids + i * k; + float * heap_dis = res->val + i * k; + const uint8_t * qcode = qcodes + i * code_size; + + if (init_finalize_heap) + maxheap_heapify (k, heap_dis, heap_ids); + + const uint8_t * bcode = bcodes; + for (size_t j = 0; j < nb; j++) { + float dis = 0; + const float * tab = sdc_table.data(); + for (int m = 0; m < M; m++) { + dis += tab[bcode[m] + qcode[m] * ksub]; + tab += ksub * ksub; + } + if (dis < heap_dis[0]) { + maxheap_pop (k, heap_dis, heap_ids); + maxheap_push (k, heap_dis, heap_ids, dis, j); + } + bcode += code_size; + } + + if (init_finalize_heap) + maxheap_reorder (k, heap_dis, heap_ids); + } + +} + + + + + + +} // namespace faiss diff --git a/ProductQuantizer.h b/ProductQuantizer.h new file mode 100644 index 000000000..4a82c40fb --- /dev/null +++ b/ProductQuantizer.h @@ -0,0 +1,175 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved. +// -*- c++ -*- + +#ifndef FAISS_PRODUCT_QUANTIZER_H +#define FAISS_PRODUCT_QUANTIZER_H + +#include + +#include + +#include "Clustering.h" +#include "Heap.h" + +namespace faiss { + +/** Product Quantizer. Implemented only for METRIC_L2 */ +struct ProductQuantizer { + + size_t d; ///< size of the input vectors + size_t M; ///< number of subquantizers + size_t nbits; ///< number of bits per quantization index + + // values derived from the above + size_t dsub; ///< dimensionality of each subvector + size_t byte_per_idx; ///< nb bytes per code component (1 or 2) + size_t code_size; ///< byte per indexed vector + size_t ksub; ///< number of centroids for each subquantizer + bool verbose; ///< verbose during training? + + + /// initialization + enum train_type_t { + Train_default, + Train_hot_start, ///< the centroids are already initialized + Train_shared, ///< share dictionary accross PQ segments + Train_hypercube, ///< intialize centroids with nbits-D hypercube + Train_hypercube_pca, ///< intialize centroids with nbits-D hypercube + }; + train_type_t train_type; + + ClusteringParameters cp; ///< parameters used during clustering + + /// Centroid table, size M * ksub * dsub + std::vector centroids; + + /// return the centroids associated with subvector m + float * get_centroids (size_t m, size_t i) { + return ¢roids [(m * ksub + i) * dsub]; + } + const float * get_centroids (size_t m, size_t i) const { + return ¢roids [(m * ksub + i) * dsub]; + } + + // Train the product quantizer on a set of points. A clustering + // can be set on input to define non-default clustering parameters + void train (int n, const float *x); + + ProductQuantizer(size_t d, /* dimensionality of the input vectors */ + size_t M, /* number of subquantizers */ + size_t nbits); /* number of bit per subvector index */ + + ProductQuantizer (); + + /// compute derived values when d, M and nbits have been set + void set_derived_values (); + + /// Define the centroids for subquantizer m + void set_params (const float * centroids, int m); + + /// Quantize one vector with the product quantizer + void compute_code (const float * x, uint8_t * code) const ; + + /// same as compute_code for several vectors + void compute_codes (const float * x, + uint8_t * codes, + size_t n) const ; + + /// decode a vector from a given code (or n vectors if third argument) + void decode (const uint8_t *code, float *x) const; + void decode (const uint8_t *code, float *x, size_t n) const; + + /// If we happen to have the distance tables precomputed, this is + /// more efficient to compute the codes. + void compute_code_from_distance_table (const float *tab, + uint8_t *code) const; + + + /** Compute distance table for one vector. + * + * The distance table for x = [x_0 x_1 .. x_(M-1)] is a M * ksub + * matrix that contains + * + * dis_table (m, j) = || x_m - c_(m, j)||^2 + * for m = 0..M-1 and j = 0 .. ksub - 1 + * + * where c_(m, j) is the centroid no j of sub-quantizer m. + * + * @param x input vector size d + * @param dis_table output table, size M * ksub + */ + void compute_distance_table (const float * x, + float * dis_table) const; + + void compute_inner_prod_table (const float * x, + float * dis_table) const; + + + /** compute distance table for several vectors + * @param nx nb of input vectors + * @param x input vector size nx * d + * @param dis_table output table, size nx * M * ksub + */ + void compute_distance_tables (size_t nx, + const float * x, + float * dis_tables) const; + + void compute_inner_prod_tables (size_t nx, + const float * x, + float * dis_tables) const; + + + /** perform a search (L2 distance) + * @param x query vectors, size nx * d + * @param nx nb of queries + * @param codes database codes, size ncodes * byte_per_idx + * @param ncodes nb of nb vectors + * @param res heap array to store results (nh == nx) + * @param init_finalize_heap initialize heap (input) and sort (output)? + */ + void search (const float * x, + size_t nx, + const uint8_t * codes, + const size_t ncodes, + float_maxheap_array_t *res, + bool init_finalize_heap = true) const; + + /** same as search, but with inner product similarity */ + void search_ip (const float * x, + size_t nx, + const uint8_t * codes, + const size_t ncodes, + float_minheap_array_t *res, + bool init_finalize_heap = true) const; + + + /// Symmetric Distance Table + std::vector sdc_table; + + // intitialize the SDC table from the centroids + void compute_sdc_table (); + + void search_sdc (const uint8_t * qcodes, + size_t nq, + const uint8_t * bcodes, + const size_t ncodes, + float_maxheap_array_t * res, + bool init_finalize_heap = true) const; + +}; + + + +} // namespace faiss + + +#endif diff --git a/README.md b/README.md new file mode 100644 index 000000000..8efb41ace --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# Faiss + +Faiss is a library for efficient similarity search and clustering of dense vectors. It contains algorithms that search in sets of vectors of any size, up to ones that possibly do not fit in RAM. It also contains supporting code for evaluation and parameter tuning. Faiss is written in C++ with complete wrappers for Python/numpy. Some of the most useful algorithms are implemented on the GPU. It is developed by [Facebook AI Research](https://research.fb.com/category/facebook-ai-research-fair/). + +## Introduction + +Faiss contains several methods for similarity search. It assumes that the instances are represented as vectors and are identified by an integer, and that the vectors can be compared with L2 distances or dot products. Vectors that are similar to a query vector are those that have the lowest L2 distance or the highest dot product with the query vector. It also supports cosine similarity, since this is a dot product on normalized vectors. + +Most of the methods, like those based on binary vectors and compact quantization codes, solely use a compressed representation of the vectors and do not require to keep the original vectors. This generally comes at the cost of a less precise search but these methods can scale to billions of vectors in main memory on a single server. + +The GPU implementation can accept input from either CPU or GPU memory. On a server with GPUs, the GPU indexes can be used a drop-in replacement for the CPU indexes (e.g., replace `IndexFlatL2` with `GpuIndexFlatL2`) and copies to/from GPU memory are handled automatically. + +## Building + +The library is mostly implemented in C++, with optional GPU support provided via CUDA, and an optional Python interface. It compiles with a Makefile. See [INSTALL](INSTALL) for details. + +## How Faiss works + +Faiss is built around an index type that stores a set of vectors, and provides a function to search in them with L2 and/or dot product vector comparison. Some index types are simple baselines, such as exact search. Most of the available indexing structures correspond to various trade-offs with respect to + +- search time +- search quality +- memory used per index vector +- training time +- need for external data for unsupervised training + +## Full documentation of Faiss + +The full documentation, including a tutorial can be found in the [wiki page](http://github.com/facebookresearch/faiss/wiki). +See the [doxygen documentation](http://rawgithub.com/facebookresearch/faiss/blob/master/docs/html/index.html) for per-class information. + +## Join the Faiss community + +We monitor the [issues page](http://github.com/facebookresearch/faiss/issues) of the repository. You can report bugs, ask questions, etc. + +## License + +Faiss is licenced under CC-by-NC, see the LICENCE file for details. This licence may be relaxed to BSD in the future. + diff --git a/VectorTransform.cpp b/VectorTransform.cpp new file mode 100644 index 000000000..c24ece29a --- /dev/null +++ b/VectorTransform.cpp @@ -0,0 +1,859 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved +// -*- c++ -*- + +#include "VectorTransform.h" + +#include +#include +#include + +#include "utils.h" +#include "FaissAssert.h" +#include "IndexPQ.h" + +using namespace faiss; + + +extern "C" { + +// this is to keep the clang syntax checker happy +#ifndef FINTEGER +#define FINTEGER int +#endif + + +/* declare BLAS functions, see http://www.netlib.org/clapack/cblas/ */ + +int sgemm_ ( + const char *transa, const char *transb, FINTEGER *m, FINTEGER * + n, FINTEGER *k, const float *alpha, const float *a, + FINTEGER *lda, const float *b, + FINTEGER *ldb, float *beta, + float *c, FINTEGER *ldc); + +int ssyrk_ ( + const char *uplo, const char *trans, FINTEGER *n, FINTEGER *k, + float *alpha, float *a, FINTEGER *lda, + float *beta, float *c, FINTEGER *ldc); + +/* Lapack functions from http://www.netlib.org/clapack/old/single/ */ + +int ssyev_ ( + const char *jobz, const char *uplo, FINTEGER *n, float *a, + FINTEGER *lda, float *w, float *work, FINTEGER *lwork, + FINTEGER *info); + +int sgesvd_( + const char *jobu, const char *jobvt, FINTEGER *m, FINTEGER *n, + float *a, FINTEGER *lda, float *s, float *u, FINTEGER *ldu, float *vt, + FINTEGER *ldvt, float *work, FINTEGER *lwork, FINTEGER *info); + +} + +/********************************************* + * VectorTransform + *********************************************/ + + + +float * VectorTransform::apply (Index::idx_t n, const float * x) const +{ + float * xt = new float[n * d_out]; + apply_noalloc (n, x, xt); + return xt; +} + + +void VectorTransform::train (idx_t, const float *) { + // does nothing by default +} + + +void VectorTransform::reverse_transform ( + idx_t , const float *, + float *) const +{ + FAISS_ASSERT (!"reverse transform not implemented"); +} + + + + +/********************************************* + * LinearTransform + *********************************************/ +/// both d_in > d_out and d_out < d_in are supported +LinearTransform::LinearTransform (int d_in, int d_out, + bool have_bias): + VectorTransform (d_in, d_out), have_bias (have_bias), + max_points_per_d (1 << 20), verbose (false) +{} + +void LinearTransform::apply_noalloc (Index::idx_t n, const float * x, + float * xt) const +{ + FAISS_ASSERT(is_trained || !"Transformation not trained yet"); + + float c_factor; + if (have_bias) { + FAISS_ASSERT (b.size() == d_out || !"Bias not initialized"); + float * xi = xt; + for (int i = 0; i < n; i++) + for(int j = 0; j < d_out; j++) + *xi++ = b[j]; + c_factor = 1.0; + } else { + c_factor = 0.0; + } + + FAISS_ASSERT (A.size() == d_out * d_in || + !"Transformation matrix not initialized"); + + float one = 1; + FINTEGER nbiti = d_out, ni = n, di = d_in; + sgemm_ ("Transposed", "Not transposed", + &nbiti, &ni, &di, + &one, A.data(), &di, x, &di, &c_factor, xt, &nbiti); + +} + + +void LinearTransform::transform_transpose (idx_t n, const float * y, + float *x) const +{ + if (have_bias) { // allocate buffer to store bias-corrected data + float *y_new = new float [n * d_out]; + const float *yr = y; + float *yw = y_new; + for (idx_t i = 0; i < n; i++) { + for (int j = 0; j < d_out; j++) { + *yw++ = *yr++ - b [j]; + } + } + y = y_new; + } + + { + FINTEGER dii = d_in, doi = d_out, ni = n; + float one = 1.0, zero = 0.0; + sgemm_ ("Not", "Not", &dii, &ni, &doi, + &one, A.data (), &dii, y, &doi, &zero, x, &dii); + } + + if (have_bias) delete [] y; +} + +const float * LinearTransform::maybe_subsample_train_set ( + Index::idx_t *n, const float *x) +{ + if (*n <= max_points_per_d * d_in) return x; + + size_t n2 = max_points_per_d * d_in; + if (verbose) { + printf (" Input training set too big, sampling " + "%ld / %ld vectors\n", n2, *n); + } + std::vector subset (*n); + rand_perm (subset.data (), *n, 1234); + float *x_subset = new float[n2 * d_in]; + for (long i = 0; i < n2; i++) + memcpy (&x_subset[i * d_in], + &x[subset[i] * size_t(d_in)], + sizeof (x[0]) * d_in); + *n = n2; + return x_subset; +} + + +/********************************************* + * RandomRotationMatrix + *********************************************/ + +void RandomRotationMatrix::init (int seed) +{ + + if(d_out <= d_in) { + A.resize (d_out * d_in); + float *q = A.data(); + float_randn(q, d_out * d_in, seed); + matrix_qr(d_in, d_out, q); + } else { + A.resize (d_out * d_out); + float *q = A.data(); + float_randn(q, d_out * d_out, seed); + matrix_qr(d_out, d_out, q); + // remove columns + int i, j; + for (i = 0; i < d_out; i++) { + for(j = 0; j < d_in; j++) { + q[i * d_in + j] = q[i * d_out + j]; + } + } + A.resize(d_in * d_out); + } + +} + +void RandomRotationMatrix::reverse_transform (idx_t n, const float * xt, + float *x) const +{ + transform_transpose (n, xt, x); +} + +/********************************************* + * PCAMatrix + *********************************************/ + +PCAMatrix::PCAMatrix (int d_in, int d_out, + float eigen_power, bool random_rotation): + LinearTransform(d_in, d_out, true), + eigen_power(eigen_power), random_rotation(random_rotation) +{ + is_trained = false; + max_points_per_d = 1000; + balanced_bins = 0; +} + + +void PCAMatrix::train (Index::idx_t n, const float *x) +{ + const float * x_in = x; + + x = maybe_subsample_train_set(&n, x); + + // compute mean + mean.clear(); mean.resize(d_in, 0.0); + if (have_bias) { // we may want to skip the bias + const float *xi = x; + for (int i = 0; i < n; i++) { + for(int j = 0; j < d_in; j++) + mean[j] += *xi++; + } + for(int j = 0; j < d_in; j++) + mean[j] /= n; + } + if(verbose) { + printf("mean=["); + for(int j = 0; j < d_in; j++) printf("%g ", mean[j]); + printf("]\n"); + } + + if(n >= d_in) { + // compute covariance matrix, store it in PCA matrix + PCAMat.resize(d_in * d_in); + float * cov = PCAMat.data(); + { // initialize with mean * mean^T term + float *ci = cov; + for(int i = 0; i < d_in; i++) { + for(int j = 0; j < d_in; j++) + *ci++ = - n * mean[i] * mean[j]; + } + } + { + FINTEGER di = d_in, ni = n; + float one = 1.0; + ssyrk_ ("Up", "Non transposed", + &di, &ni, &one, (float*)x, &di, &one, cov, &di); + + } + if(verbose && d_in <= 10) { + float *ci = cov; + printf("cov=\n"); + for(int i = 0; i < d_in; i++) { + for(int j = 0; j < d_in; j++) + printf("%10g ", *ci++); + printf("\n"); + } + } + + { // compute eigenvalues and vectors + eigenvalues.resize(d_in); + FINTEGER info = 0, lwork = -1, di = d_in; + float workq; + + ssyev_ ("Vectors as well", "Upper", + &di, cov, &di, eigenvalues.data(), &workq, &lwork, &info); + lwork = FINTEGER(workq); + float *work = new float[lwork]; + + ssyev_ ("Vectors as well", "Upper", + &di, cov, &di, eigenvalues.data(), work, &lwork, &info); + + if (info != 0) { + fprintf (stderr, "WARN ssyev info returns %d, " + "a very bad PCA matrix is learnt\n", + int(info)); + + } + + delete [] work; + + if(verbose && d_in <= 10) { + printf("info=%ld new eigvals=[", long(info)); + for(int j = 0; j < d_in; j++) printf("%g ", eigenvalues[j]); + printf("]\n"); + + float *ci = cov; + printf("eigenvecs=\n"); + for(int i = 0; i < d_in; i++) { + for(int j = 0; j < d_in; j++) + printf("%10.4g ", *ci++); + printf("\n"); + } + } + + } + + // revert order of eigenvectors & values + + for(int i = 0; i < d_in / 2; i++) { + + std::swap(eigenvalues[i], eigenvalues[d_in - 1 - i]); + float *v1 = cov + i * d_in; + float *v2 = cov + (d_in - 1 - i) * d_in; + for(int j = 0; j < d_in; j++) + std::swap(v1[j], v2[j]); + } + + } else { + FAISS_ASSERT(!"Gramm matrix version not implemented " + "-- provide more training examples than dimensions"); + } + + + if (x != x_in) delete [] x; + + prepare_Ab(); + is_trained = true; +} + +void PCAMatrix::copy_from (const PCAMatrix & other) +{ + FAISS_ASSERT (other.is_trained); + mean = other.mean; + eigenvalues = other.eigenvalues; + PCAMat = other.PCAMat; + prepare_Ab (); + is_trained = true; +} + +void PCAMatrix::prepare_Ab () +{ + + if (!random_rotation) { + A = PCAMat; + A.resize(d_out * d_in); // strip off useless dimensions + + // first scale the components + if (eigen_power != 0) { + float *ai = A.data(); + for (int i = 0; i < d_out; i++) { + float factor = pow(eigenvalues[i], eigen_power); + for(int j = 0; j < d_in; j++) + *ai++ *= factor; + } + } + + if (balanced_bins != 0) { + FAISS_ASSERT (d_out % balanced_bins == 0); + int dsub = d_out / balanced_bins; + std::vector Ain; + std::swap(A, Ain); + A.resize(d_out * d_in); + + std::vector accu(balanced_bins); + std::vector counter(balanced_bins); + + // greedy assignment + for (int i = 0; i < d_out; i++) { + // find best bin + int best_j = -1; + float min_w = 1e30; + for (int j = 0; j < balanced_bins; j++) { + if (counter[j] < dsub && accu[j] < min_w) { + min_w = accu[j]; + best_j = j; + } + } + int row_dst = best_j * dsub + counter[best_j]; + accu[best_j] += eigenvalues[i]; + counter[best_j] ++; + memcpy (&A[row_dst * d_in], &Ain[i * d_in], + d_in * sizeof (A[0])); + } + + if (verbose) { + printf(" bin accu=["); + for (int i = 0; i < balanced_bins; i++) + printf("%g ", accu[i]); + printf("]\n"); + } + } + + + } else { + FAISS_ASSERT (balanced_bins == 0 || + !"both balancing bins and applying a random rotation " + "does not make sense"); + RandomRotationMatrix rr(d_out, d_out); + + rr.init(5); + + // apply scaling on the rotation matrix (right multiplication) + if (eigen_power != 0) { + for (int i = 0; i < d_out; i++) { + float factor = pow(eigenvalues[i], eigen_power); + for(int j = 0; j < d_out; j++) + rr.A[j * d_out + i] *= factor; + } + } + + A.resize(d_in * d_out); + { + FINTEGER dii = d_in, doo = d_out; + float one = 1.0, zero = 0.0; + + sgemm_ ("Not", "Not", &dii, &doo, &doo, + &one, PCAMat.data(), &dii, rr.A.data(), &doo, &zero, + A.data(), &dii); + + } + + } + + b.clear(); b.resize(d_out); + + for (int i = 0; i < d_out; i++) { + float accu = 0; + for (int j = 0; j < d_in; j++) + accu -= mean[j] * A[j + i * d_in]; + b[i] = accu; + } + +} + +void PCAMatrix::reverse_transform (idx_t n, const float * xt, + float *x) const +{ + FAISS_ASSERT (eigen_power == 0 || + !"reverse only implemented for orthogonal transforms"); + transform_transpose (n, xt, x); +} + +/********************************************* + * OPQMatrix + *********************************************/ + + +OPQMatrix::OPQMatrix (int d, int M, int d2): + LinearTransform (d, d2 == -1 ? d : d2, false), M(M), + niter (50), + niter_pq (4), niter_pq_0 (40), + verbose(false) +{ + is_trained = false; + max_points_per_d = 1000; +} + + + +void OPQMatrix::train (Index::idx_t n, const float *x) +{ + + const float * x_in = x; + + x = maybe_subsample_train_set (&n, x); + + // To support d_out > d_in, we pad input vectors with 0s to d_out + size_t d = d_out <= d_in ? d_in : d_out; + size_t d2 = d_out; + +#if 0 + // what this test shows: the only way of getting bit-exact + // reproducible results with sgeqrf and sgesvd seems to be forcing + // single-threading. + { // test repro + std::vector r (d * d); + float * rotation = r.data(); + float_randn (rotation, d * d, 1234); + printf("CS0: %016lx\n", + ivec_checksum (128*128, (int*)rotation)); + matrix_qr (d, d, rotation); + printf("CS1: %016lx\n", + ivec_checksum (128*128, (int*)rotation)); + return; + } +#endif + + if (verbose) { + printf ("OPQMatrix::train: training an OPQ rotation matrix " + "for M=%d from %ld vectors in %dD -> %dD\n", + M, n, d_in, d_out); + } + + std::vector xtrain (n * d); + // center x + { + std::vector sum (d); + const float *xi = x; + for (size_t i = 0; i < n; i++) { + for (int j = 0; j < d_in; j++) + sum [j] += *xi++; + } + for (int i = 0; i < d; i++) sum[i] /= n; + float *yi = xtrain.data(); + xi = x; + for (size_t i = 0; i < n; i++) { + for (int j = 0; j < d_in; j++) + *yi++ = *xi++ - sum[j]; + yi += d - d_in; + } + } + float *rotation; + + if (A.size () == 0) { + A.resize (d * d); + rotation = A.data(); + if (verbose) + printf(" OPQMatrix::train: making random %ld*%ld rotation\n", + d, d); + float_randn (rotation, d * d, 1234); + matrix_qr (d, d, rotation); + // we use only the d * d2 upper part of the matrix + A.resize (d * d2); + } else { + FAISS_ASSERT (A.size() == d * d2); + rotation = A.data(); + } + + + std::vector + xproj (d2 * n), pq_recons (d2 * n), xxr (d * n), + tmp(d * d * 4); + + std::vector codes (M * n); + ProductQuantizer pq_regular (d2, M, 8); + double t0 = getmillisecs(); + for (int iter = 0; iter < niter; iter++) { + + { // torch.mm(xtrain, rotation:t()) + FINTEGER di = d, d2i = d2, ni = n; + float zero = 0, one = 1; + sgemm_ ("Transposed", "Not transposed", + &d2i, &ni, &di, + &one, rotation, &di, + xtrain.data(), &di, + &zero, xproj.data(), &d2i); + } + + pq_regular.cp.max_points_per_centroid = 1000; + pq_regular.cp.niter = iter == 0 ? niter_pq_0 : niter_pq; + pq_regular.cp.verbose = verbose; + pq_regular.train (n, xproj.data()); + + pq_regular.compute_codes (xproj.data(), codes.data(), n); + pq_regular.decode (codes.data(), pq_recons.data(), n); + + float pq_err = fvec_L2sqr (pq_recons.data(), xproj.data(), n * d2) / n; + + if (verbose) + printf (" Iteration %d (%d PQ iterations):" + "%.3f s, obj=%g\n", iter, pq_regular.cp.niter, + (getmillisecs () - t0) / 1000.0, pq_err); + + { + float *u = tmp.data(), *vt = &tmp [d * d]; + float *sing_val = &tmp [2 * d * d]; + FINTEGER di = d, d2i = d2, ni = n; + float one = 1, zero = 0; + + // torch.mm(xtrain:t(), pq_recons) + sgemm_ ("Not", "Transposed", + &d2i, &di, &ni, + &one, pq_recons.data(), &d2i, + xtrain.data(), &di, + &zero, xxr.data(), &d2i); + + + FINTEGER lwork = -1, info = -1; + float worksz; + // workspace query + sgesvd_ ("All", "All", + &d2i, &di, xxr.data(), &d2i, + sing_val, + vt, &d2i, u, &di, + &worksz, &lwork, &info); + + lwork = int(worksz); + std::vector work (lwork); + // u and vt swapped + sgesvd_ ("All", "All", + &d2i, &di, xxr.data(), &d2i, + sing_val, + vt, &d2i, u, &di, + work.data(), &lwork, &info); + + sgemm_ ("Transposed", "Transposed", + &di, &d2i, &d2i, + &one, u, &di, vt, &d2i, + &zero, rotation, &di); + + } + pq_regular.train_type = ProductQuantizer::Train_hot_start; + } + + // revert A matrix + if (d > d_in) { + for (long i = 0; i < d_out; i++) + memmove (&A[i * d_in], &A[i * d], sizeof(A[0]) * d_in); + A.resize (d_in * d_out); + } + + if (x != x_in) + delete [] x; + + is_trained = true; +} + + + + +void OPQMatrix::reverse_transform (idx_t n, const float * xt, + float *x) const +{ + transform_transpose (n, xt, x); +} + +/********************************************* + * IndexPreTransform + *********************************************/ + +IndexPreTransform::IndexPreTransform (): + index(nullptr), own_fields (false) +{ +} + + +IndexPreTransform::IndexPreTransform ( + Index * index): + Index (index->d, index->metric_type), + index (index), own_fields (false) +{ + is_trained = index->is_trained; + set_typename(); +} + + + + +IndexPreTransform::IndexPreTransform ( + VectorTransform * ltrans, + Index * index): + Index (index->d, index->metric_type), + index (index), own_fields (false) +{ + is_trained = index->is_trained; + prepend_transform (ltrans); + set_typename(); +} + +void IndexPreTransform::prepend_transform (VectorTransform *ltrans) +{ + FAISS_ASSERT (ltrans->d_out == d); + is_trained = is_trained && ltrans->is_trained; + chain.insert (chain.begin(), ltrans); + d = ltrans->d_in; + set_typename (); +} + + +void IndexPreTransform::set_typename () +{ + // TODO correct this according to actual type + index_typename = "PreLT[" + index->index_typename + "]"; +} + + +IndexPreTransform::~IndexPreTransform () +{ + if (own_fields) { + for (int i = 0; i < chain.size(); i++) + delete chain[i]; + delete index; + } +} + + + + +void IndexPreTransform::train (idx_t n, const float *x) +{ + int last_untrained = 0; + for (int i = 0; i < chain.size(); i++) + if (!chain[i]->is_trained) last_untrained = i; + if (!index->is_trained) last_untrained = chain.size(); + const float *prev_x = x; + + for (int i = 0; i <= last_untrained; i++) { + if (i < chain.size()) { + VectorTransform *ltrans = chain [i]; + if (!ltrans->is_trained) + ltrans->train(n, prev_x); + } else { + index->train (n, prev_x); + } + if (i == last_untrained) break; + + float * xt = chain[i]->apply (n, prev_x); + if (prev_x != x) delete [] prev_x; + prev_x = xt; + } + + if (prev_x != x) delete [] prev_x; + is_trained = true; +} + + +const float *IndexPreTransform::apply_chain (idx_t n, const float *x) const +{ + const float *prev_x = x; + for (int i = 0; i < chain.size(); i++) { + float * xt = chain[i]->apply (n, prev_x); + if (prev_x != x) delete [] prev_x; + prev_x = xt; + } + return prev_x; +} + +void IndexPreTransform::add (idx_t n, const float *x) +{ + FAISS_ASSERT (is_trained); + const float *xt = apply_chain (n, x); + index->add (n, xt); + if (xt != x) delete [] xt; + ntotal = index->ntotal; +} + +void IndexPreTransform::add_with_ids (idx_t n, const float * x, + const long *xids) +{ + FAISS_ASSERT (is_trained); + const float *xt = apply_chain (n, x); + index->add_with_ids (n, xt, xids); + if (xt != x) delete [] xt; + ntotal = index->ntotal; +} + + + + +void IndexPreTransform::search (idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const +{ + FAISS_ASSERT (is_trained); + const float *xt = apply_chain (n, x); + index->search (n, xt, k, distances, labels); + if (xt != x) delete [] xt; +} + + +void IndexPreTransform::reset () { + index->reset(); + ntotal = 0; +} + +long IndexPreTransform::remove_ids (const IDSelector & sel) { + long nremove = index->remove_ids (sel); + ntotal = index->ntotal; + return nremove; +} + + +void IndexPreTransform::reconstruct_n (idx_t i0, idx_t ni, float *recons) const +{ + float *x = chain.empty() ? recons : new float [ni * index->d]; + // initial reconstruction + index->reconstruct_n (i0, ni, x); + + // revert transformations from last to first + for (int i = chain.size() - 1; i >= 0; i--) { + float *x_pre = i == 0 ? recons : new float [chain[i]->d_in * ni]; + chain [i]->reverse_transform (ni, x, x_pre); + delete [] x; + x = x_pre; + } +} + + + +/********************************************* + * RemapDimensionsTransform + *********************************************/ + + +RemapDimensionsTransform::RemapDimensionsTransform ( + int d_in, int d_out, const int *map_in): + VectorTransform (d_in, d_out) +{ + map.resize (d_out); + for (int i = 0; i < d_out; i++) { + map[i] = map_in[i]; + FAISS_ASSERT (map[i] == -1 || (map[i] >= 0 && map[i] < d_in)); + } +} + +RemapDimensionsTransform::RemapDimensionsTransform ( + int d_in, int d_out, bool uniform): VectorTransform (d_in, d_out) +{ + map.resize (d_out, -1); + + if (uniform) { + if (d_in < d_out) { + for (int i = 0; i < d_in; i++) { + map [i * d_out / d_in] = i; + } + } else { + for (int i = 0; i < d_out; i++) { + map [i] = i * d_in / d_out; + } + } + } else { + for (int i = 0; i < d_in && i < d_out; i++) + map [i] = i; + } +} + + +void RemapDimensionsTransform::apply_noalloc (idx_t n, const float * x, + float *xt) const +{ + for (idx_t i = 0; i < n; i++) { + for (int j = 0; j < d_out; j++) { + xt[j] = map[j] < 0 ? 0 : x[map[j]]; + } + x += d_in; + xt += d_out; + } +} + +void RemapDimensionsTransform::reverse_transform (idx_t n, const float * xt, + float *x) const +{ + memset (x, 0, sizeof (*x) * n * d_in); + for (idx_t i = 0; i < n; i++) { + for (int j = 0; j < d_out; j++) { + if (map[j] >= 0) x[map[j]] = xt[j]; + } + x += d_in; + xt += d_out; + } +} diff --git a/VectorTransform.h b/VectorTransform.h new file mode 100644 index 000000000..0bc1ca213 --- /dev/null +++ b/VectorTransform.h @@ -0,0 +1,291 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the CC-by-NC license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Copyright 2004-present Facebook. All Rights Reserved. +// -*- c++ -*- + +#ifndef FAISS_VECTOR_TRANSFORM_H +#define FAISS_VECTOR_TRANSFORM_H + +/** Defines a few objects that apply transformations to a set of + * vectors Often these are pre-processing steps. + */ + +#include + +#include "Index.h" + + +namespace faiss { + + +/** Any transformation applied on a set of vectors */ +struct VectorTransform { + + typedef Index::idx_t idx_t; + + int d_in; ///! input dimension + int d_out; ///! output dimension + + explicit VectorTransform (int d_in = 0, int d_out = 0): + d_in(d_in), d_out(d_out), is_trained(true) + {} + + + /// set if the LinearTransform does not require training, or if + /// training is done already + bool is_trained; + + + /** Perform training on a representative set of vectors. Does + * nothing by default. + * + * @param n nb of training vectors + * @param x training vecors, size n * d + */ + virtual void train (idx_t n, const float *x); + + /** apply the random roation, return new allocated matrix + * @param x size n * d_in + * @return size n * d_out + */ + float *apply (idx_t n, const float * x) const; + + /// same as apply, but result is pre-allocated + virtual void apply_noalloc (idx_t n, const float * x, + float *xt) const = 0; + + /// reverse transformation. May not be implemented or may return + /// approximate result + virtual void reverse_transform (idx_t n, const float * xt, + float *x) const; + + virtual ~VectorTransform () {} + +}; + + + +/** Generic linear transformation, with bias term applied on output + * y = A * x + b + */ +struct LinearTransform: VectorTransform { + + + bool have_bias; ///! whether to use the bias term + + /// Transformation matrix, size d_out * d_in + std::vector A; + + /// bias vector, size d_out + std::vector b; + + + /// both d_in > d_out and d_out < d_in are supported + explicit LinearTransform (int d_in = 0, int d_out = 0, + bool have_bias = false); + + /// same as apply, but result is pre-allocated + virtual void apply_noalloc (idx_t n, const float * x, + float *xt) const; + + /// compute x = A^T * (x - b) + /// is reverse transform if A has orthonormal lines + void transform_transpose (idx_t n, const float * y, + float *x) const; + + // ratio between # training vectors and dimension + size_t max_points_per_d; + bool verbose; + + // subsamples training set if there are too many vectors + const float *maybe_subsample_train_set (Index::idx_t *n, const float *x); + + virtual ~LinearTransform () {} + + +}; + + + +/// Randomly rotate a set of vectors +struct RandomRotationMatrix: LinearTransform { + + /// both d_in > d_out and d_out < d_in are supported + RandomRotationMatrix (int d_in, int d_out): + LinearTransform(d_in, d_out, false) {} + + /// must be called before the transform is used + void init(int seed); + + virtual void reverse_transform (idx_t n, const float * xt, + float *x) const override; + + RandomRotationMatrix () {} +}; + + +/** Applies a principal component analysis on a set of vectors, + * with optionally whitening and random rotation. */ +struct PCAMatrix: LinearTransform { + + /** after transformation the components are multiplied by + * eigenvalues^eigen_power + * + * =0: no whitening + * =-2: full whitening + */ + float eigen_power; + + /// random rotation after PCA + bool random_rotation; + + /// try to distribute output eigenvectors in this many bins + int balanced_bins; + + /// Mean, size d_in + std::vector mean; + + /// eigenvalues of covariance matrix (= squared singular values) + std::vector eigenvalues; + + /// PCA matrix, size d_in * d_in + std::vector PCAMat; + + // the final matrix is computed after random rotation and/or whitening + explicit PCAMatrix (int d_in = 0, int d_out = 0, + float eigen_power = 0, bool random_rotation = false); + + virtual void train (Index::idx_t n, const float *x) override; + + virtual void reverse_transform (idx_t n, const float * xt, + float *x) const override; + + /// copy pre-trained PCA matrix + void copy_from (const PCAMatrix & other); + + /// called after mean, PCAMat and eigenvalues are computed + void prepare_Ab(); + +}; + + + +/** Applies a rotation to align the dimensions with a PQ to minimize + * the reconstruction error. Can be used before an IndexPQ or an + * IndexIVFPQ. The method is the non-parametric version described in: + * + * "Optimized Product Quantization for Approximate Nearest Neighbor Search" + * Tiezheng Ge, Kaiming He, Qifa Ke, Jian Sun, CVPR'13 + * + */ +struct OPQMatrix: LinearTransform { + + int M; ///< nb of subquantizers + int niter; ///< Number of outer training iterations + int niter_pq; ///< Number of training iterations for the PQ + int niter_pq_0; ///< same, for the first outer iteration + /// if there are too many training points, resample + int max_points_per_d; + bool verbose; + + /// if d2 != -1, output vectors of this dimension + explicit OPQMatrix (int d = 0, int M = 1, int d2 = -1); + + virtual void train (Index::idx_t n, const float *x) override; + + virtual void reverse_transform (idx_t n, const float * xt, + float *x) const override; +}; + + +/** remap dimensions for intput vectors, possibly inserting 0s + * strictly speaking this is also a linear transform but we don't want + * to compute it with matrix multiplies */ +struct RemapDimensionsTransform: VectorTransform { + + + /// map from output dimension to input, size d_out + /// -1 -> set output to 0 + std::vector map; + + RemapDimensionsTransform (int d_in, int d_out, const int *map); + + /// remap input to output, skipping or inserting dimensions as needed + /// if uniform: distribute dimensions uniformly + /// otherwise just take the d_out first ones. + RemapDimensionsTransform (int d_in, int d_out, bool uniform = true); + + virtual void apply_noalloc (idx_t n, const float * x, + float *xt) const override; + + /// reverse transform correct only when the mapping is a permuation + virtual void reverse_transform (idx_t n, const float * xt, + float *x) const override; + + RemapDimensionsTransform () {} +}; + + +/** Index that applies a LinearTransform transform on vectors before + * handing them over to a sub-index */ +struct IndexPreTransform: Index { + + std::vector chain; ///! chain of tranforms + Index * index; ///! the sub-index + + bool own_fields; ///! whether pointers are deleted in destructor + + explicit IndexPreTransform (Index *index); + + IndexPreTransform (); + + /// ltrans is the last transform before the index + IndexPreTransform (VectorTransform * ltrans, Index * index); + + void prepend_transform (VectorTransform * ltrans); + + virtual void set_typename () override; + + virtual void train (idx_t n, const float *x) override; + + virtual void add (idx_t n, const float *x) override; + + virtual void add_with_ids (idx_t n, const float * x, const long *xids) + override; + + + virtual void reset () override; + + /** removes IDs from the index. Not supported by all indexes. + */ + virtual long remove_ids (const IDSelector & sel) override; + + virtual void search ( + idx_t n, const float *x, idx_t k, + float *distances, idx_t *labels) const override; + + void reconstruct_n (idx_t i0, idx_t ni, float *recons) + const override; + + /// apply the transforms in the chain. The returned float * may be + /// equal to x, otherwise it should be deallocated. + const float * apply_chain (idx_t n, const float *x) const; + + virtual ~IndexPreTransform (); + +}; + + + +} // namespace faiss + + + +#endif diff --git a/benchs/README.md b/benchs/README.md new file mode 100644 index 000000000..6a383d7c2 --- /dev/null +++ b/benchs/README.md @@ -0,0 +1,243 @@ + +# Benchmarking scripts + +This directory contains benchmarking scripts that can reproduce the +numbers reported in the two papers + +``` +@inproceedings{DJP16, + Author = {Douze, Matthijs and J{\'e}gou, Herv{\'e} and Perronnin, Florent}, + Booktitle = "ECCV", + Organization = {Springer}, + Title = {Polysemous codes}, + Year = {2016} +} +``` +and + +ARXIV paper here + + + +Note that the numbers (especially timings) change slightly due to improvements in the implementation, different machines, etc. + +The scripts are self-contained. They depend only on Faiss and external training data that should be stored in sub-directories. + +## SIFT1M experiments + +The script [`bench_polysemous_sift1m.py`](bench_polysemous_sift1m.py) reproduces the numbers in +Figure 3 from the "Polysemous" paper. + +### Getting SIFT1M + +To run it, please download the ANN_SIFT1M dataset from + +http://corpus-texmex.irisa.fr/ + +and unzip it to the sudirectory sift1M. + +### Result + +The output looks like: + +``` +PQ training on 100000 points, remains 0 points: training polysemous on centroids +add vectors to index +PQ baseline 7.517 ms per query, R@1 0.4474 +Polysemous 64 9.875 ms per query, R@1 0.4474 +Polysemous 62 8.358 ms per query, R@1 0.4474 +Polysemous 58 5.531 ms per query, R@1 0.4474 +Polysemous 54 3.420 ms per query, R@1 0.4478 +Polysemous 50 2.182 ms per query, R@1 0.4475 +Polysemous 46 1.621 ms per query, R@1 0.4408 +Polysemous 42 1.448 ms per query, R@1 0.4174 +Polysemous 38 1.331 ms per query, R@1 0.3563 +Polysemous 34 1.334 ms per query, R@1 0.2661 +Polysemous 30 1.272 ms per query, R@1 0.1794 +``` + + +## Experiments on 1B elements dataset + +The script [`bench_polysemous_1bn.py`](bench_polysemous_1bn.py) reproduces a few experiments on +two datasets of size 1B from the Polysemous codes" paper. + + +### Getting BIGANN + +Download the four files of ANN_SIFT1B from +http://corpus-texmex.irisa.fr/ to subdirectory bigann/ + +### Getting Deep1B + +The ground-truth and queries are available here + +https://yadi.sk/d/11eDCm7Dsn9GA + +For the learning and database vectors, use the script + +https://github.com/arbabenko/GNOIMI/blob/master/downloadDeep1B.py + +to download the data to subdirectory deep1b/, then concatenate the +database files to base.fvecs and the training files to learn.fvecs + +### Running the experiments + +These experiments are quite long. To support resuming, the script +stores the result of raining to a temporary directory, `/tmp/bench_polysemous`. + +The script `bench_polysemous_1bn.py` takes at leas two arguments: + +- the dataset name: SIFT1000M (aka SIFT1B, aka BIGANN) or Deep1B. SIFT1M, SIFT2M,... are also supported to make subsets of for small experiments (note that SIFT1M is not the same as the SIFT1M above) + +- the type of index to build, which should be a valid [index_factory key](https://github.com/facebookresearch/faiss/wiki/High-level-interface-and-auto-tuning#index-factory) (see below for examples) + +- the remaining arguments are parsed as search-time parameters. + +### Experiments of Table 2 + +The `IMI*+PolyD+ADC` results in Table 2 can be reproduced with (for 16 bytes): + +``` +python bench_polysemous_1bn.par SIFT1000M IMI2x12,PQ16 nprobe=16,max_codes={10000,30000},ht={44..54} +``` + +Training takes about 2 minutes and adding vectors to the dataset +takes 3.1 h. These operations are multithreaded. Note that in the command +above, we use bash's [brace expansion](https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html) to set a grid of parameters. + +The search is *not* multithreaded, and the output looks like: + +``` + R@1 R@10 R@100 time %pass +nprobe=16,max_codes=10000,ht=44 0.1779 0.2994 0.3139 0.194 12.45 +nprobe=16,max_codes=10000,ht=45 0.1859 0.3183 0.3339 0.197 14.24 +nprobe=16,max_codes=10000,ht=46 0.1930 0.3366 0.3543 0.202 16.22 +nprobe=16,max_codes=10000,ht=47 0.1993 0.3550 0.3745 0.209 18.39 +nprobe=16,max_codes=10000,ht=48 0.2033 0.3694 0.3917 0.640 20.77 +nprobe=16,max_codes=10000,ht=49 0.2070 0.3839 0.4077 0.229 23.36 +nprobe=16,max_codes=10000,ht=50 0.2101 0.3949 0.4205 0.232 26.17 +nprobe=16,max_codes=10000,ht=51 0.2120 0.4042 0.4310 0.239 29.21 +nprobe=16,max_codes=10000,ht=52 0.2134 0.4113 0.4402 0.245 32.47 +nprobe=16,max_codes=10000,ht=53 0.2157 0.4184 0.4482 0.250 35.96 +nprobe=16,max_codes=10000,ht=54 0.2170 0.4240 0.4546 0.256 39.66 +nprobe=16,max_codes=30000,ht=44 0.1882 0.3327 0.3555 0.226 11.29 +nprobe=16,max_codes=30000,ht=45 0.1964 0.3525 0.3771 0.231 13.05 +nprobe=16,max_codes=30000,ht=46 0.2039 0.3713 0.3987 0.236 15.01 +nprobe=16,max_codes=30000,ht=47 0.2103 0.3907 0.4202 0.245 17.19 +nprobe=16,max_codes=30000,ht=48 0.2145 0.4055 0.4384 0.251 19.60 +nprobe=16,max_codes=30000,ht=49 0.2179 0.4198 0.4550 0.257 22.25 +nprobe=16,max_codes=30000,ht=50 0.2208 0.4305 0.4681 0.268 25.15 +nprobe=16,max_codes=30000,ht=51 0.2227 0.4402 0.4791 0.275 28.30 +nprobe=16,max_codes=30000,ht=52 0.2241 0.4473 0.4884 0.284 31.70 +nprobe=16,max_codes=30000,ht=53 0.2265 0.4544 0.4965 0.294 35.34 +nprobe=16,max_codes=30000,ht=54 0.2278 0.4601 0.5031 0.303 39.20 +``` + +The result reported in table 2 is the one for which the %pass (percentage of code comparisons that pass the Hamming check) is around 20%, which occurs for Hamming threshold `ht=48`. + +The 8-byte results can be reproduced with the factory key `IMI2x12,PQ8` + +### Experiments of the appendix + +The experiments in the appendix are only in the ArXiv version of the paper (table 3). + +``` +python bench_polysemous_1bn.py SIFT1000M OPQ8_64,IMI2x13,PQ8 nprobe={1,2,4,8,16,32,64,128},ht={20,24,26,28,30} + + R@1 R@10 R@100 time %pass +nprobe=1,ht=20 0.0351 0.0616 0.0751 0.158 19.01 +... +nprobe=32,ht=28 0.1256 0.3563 0.5026 0.561 52.61 +... +``` +Here again the runs are not exactly the same but the original result was obtained from nprobe=32,ht=28. + +``` +python bench_polysemous_1bn.py Deep1B OPQ20_80,IMI2x14,PQ20 autotune +... +Done in 4067.555 s, available OPs: +Parameters 1-R@1 time + 0.0000 0.000 +nprobe=1,ht=22,max_codes=256 0.0215 3.115 +nprobe=1,ht=30,max_codes=256 0.0381 3.120 +... +nprobe=512,ht=68,max_codes=524288 0.4478 36.903 +nprobe=1024,ht=80,max_codes=131072 0.4557 46.363 +nprobe=1024,ht=78,max_codes=262144 0.4616 61.939 +... +``` +The original results were obtained with `nprobe=1024,ht=66,max_codes=262144`. + + +## GPU experiments + +The benchmarks below run a Titan X GPU. They are also a good starting point for further optimization, because + + +### Exact search on SIFT1M + +See above on how to get SIFT1M into subdirectory sift1M/. The script [`bench_gpu_sift1m.py`](bench_gpu_sift1m.py) reproduces the "exact k-NN time" plot in the ArXiv paper, and the SIFT1M numbers. + +The output is: +``` +============ Exact search +add vectors to index +warmup +benchmark +k=1 0.715 s, R@1 0.9914 +k=2 0.729 s, R@1 0.9935 +k=4 0.731 s, R@1 0.9935 +k=8 0.732 s, R@1 0.9935 +k=16 0.742 s, R@1 0.9935 +k=32 0.737 s, R@1 0.9935 +k=64 0.753 s, R@1 0.9935 +k=128 0.761 s, R@1 0.9935 +k=256 0.799 s, R@1 0.9935 +k=512 0.975 s, R@1 0.9935 +k=1024 1.424 s, R@1 0.9935 +============ Approximate search +train +WARNING clustering 100000 points to 4096 centroids: please provide at least 159744 training points +add vectors to index +WARN: increase temp memory to avoid cudaMalloc, or decrease query/add size (alloc 256000000 B, highwater 256000000 B) +warmup +benchmark +nprobe= 1 0.043 s recalls= 0.3909 0.4312 0.4312 +nprobe= 2 0.040 s recalls= 0.5041 0.5636 0.5636 +nprobe= 4 0.048 s recalls= 0.6048 0.6897 0.6897 +nprobe= 8 0.064 s recalls= 0.6879 0.8028 0.8028 +nprobe= 16 0.088 s recalls= 0.7534 0.8940 0.8940 +nprobe= 32 0.134 s recalls= 0.7957 0.9549 0.9550 +nprobe= 64 0.224 s recalls= 0.8125 0.9833 0.9834 +nprobe= 128 0.395 s recalls= 0.8205 0.9953 0.9954 +nprobe= 256 0.717 s recalls= 0.8227 0.9993 0.9994 +nprobe= 512 1.348 s recalls= 0.8228 0.9999 1.0000 +``` +The run produces two warnings: + +- the clustering complains that it does not have enough training data, there is not much we can do about this. + +- the add() function complains that there is an inefficient memory allocation, but this is a concern only when it happens often. + +### Clustering on MNIST8m + +To get the "infinite MNIST dataset", follow the instructions on [Léon Bottou's website](http://leon.bottou.org/projects/infimnist). The script assumes the file `mnist8m-patterns-idx3-ubyte` is in subdirectory `mnist8m` + +The script [`kmeans_mnist.py`](kmeans_mnist.py) produces the following output: + +``` +python kmeans_mnist.py 1 256 +... +Clustering 8100000 points in 784D to 256 clusters, redo 1 times, 20 iterations + Preprocessing in 7.94526 s + Iteration 19 (131.697 s, search 114.78 s): objective=1.44881e+13 imbalance=1.05963 nsplit=0 +final objective: 1.449e+13 +total runtime: 140.615 s +``` + +### search on SIFT1B + + +### knn-graph on Deep1B + diff --git a/docs/Doxyfile b/docs/Doxyfile new file mode 100644 index 000000000..d3a9ce7d1 --- /dev/null +++ b/docs/Doxyfile @@ -0,0 +1,2281 @@ + +# Doxyfile 1.8.5 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "Faiss" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is included in +# the documentation. The maximum height of the logo should not exceed 55 pixels +# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo +# to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese- +# Traditional, Croatian, Czech, Danish, Dutch, English, Esperanto, Farsi, +# Finnish, French, German, Greek, Hungarian, Italian, Japanese, Japanese-en, +# Korean, Korean-en, Latvian, Norwegian, Macedonian, Persian, Polish, +# Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, +# Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a +# new page for each member. If set to NO, the documentation of a member will be +# part of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. +# +# Note For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by by putting a % sign in front of the word +# or globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO these classes will be included in the various overviews. This option has +# no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the +# todo list. This list is created by putting \todo commands in the +# documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the +# test list. This list is created by putting \test commands in the +# documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES the list +# will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. Do not use file names with spaces, bibtex cannot handle them. See +# also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO doxygen will only warn about wrong or incomplete parameter +# documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. +# Note: If this tag is empty the current directory is searched. + +INPUT = ../ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank the +# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, +# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, +# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, +# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, +# *.qsf, *.as and *.js. + +FILE_PATTERNS = *.cpp *.h *.cu *.cuh + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER ) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = NO + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES, then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- +# defined cascading style sheet that is included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefor more robust against future updates. +# Doxygen will copy the style sheet file to the output directory. For an example +# see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the stylesheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler ( hhc.exe). If non-empty +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated ( +# YES) or that it should be included in the master .chm file ( NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated ( +# YES) or a normal table of contents ( NO) in the .chm file. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /