1
1
import numpy as np
2
2
import faiss
3
3
4
+ import threading
5
+ import queue
6
+ import time
7
+
4
8
model_location = '/data/model_hf'
5
9
6
10
class Faiss :
@@ -13,6 +17,30 @@ def __init__(self):
13
17
self .modelLoaded = self .loadModelFromDisk (model_location )
14
18
self .is_initiated = self .modelLoaded
15
19
20
+ # to keep the thread & queue running
21
+ self .process_flag = True
22
+ self .q_maxsize = 10100
23
+ self .process_thread = None
24
+ self ._lock = threading .Lock ()
25
+ self .process_timeout_sec = 5 # seconds
26
+
27
+ # spawn process thread
28
+ self .spawn ()
29
+
30
+ def __del__ (self ):
31
+ self .process_flag = False
32
+ if self .process_thread :
33
+ self .process_thread .join ()
34
+
35
+ def spawn (self ):
36
+ # create pipeline to add documents
37
+ self .pipeline = queue .Queue (maxsize = self .q_maxsize )
38
+ # create process thread
39
+ self .process_thread = threading .Thread (target = self .process , args = (), daemon = True )
40
+ # start process thread
41
+ self .process_thread .start ()
42
+ # return self.pipeline
43
+
16
44
def initFaiss (self , nlist , nprobe , bytesPerVec , bytesPerSubVec , dim , matrix ):
17
45
self .nlist = nlist
18
46
self .nprobe = nprobe
@@ -23,14 +51,18 @@ def initFaiss(self, nlist, nprobe, bytesPerVec, bytesPerSubVec, dim, matrix):
23
51
self .train_data = np .matrix (matrix ).astype ('float32' )
24
52
print ('FAISS init quantizer' , self .train_data , self .train_data .shape )
25
53
self .f_quantizer = faiss .IndexFlatL2 (self .dim )
26
- print ('FAISS init index' )
27
- self .f_index = faiss .IndexIVFPQ (self .f_quantizer , self .dim , self .nlist , self .bytesPerVec , self .bytesPerSubVec )
28
- print ('FAISS train index' )
29
- self .f_index .train (self .train_data )
30
- print ('FAISS train index finished' )
54
+ # Lock index read / wtite until it is built
55
+ with self ._lock :
56
+ print ('FAISS init index' )
57
+ self .f_index = faiss .IndexIVFPQ (self .f_quantizer , self .dim , self .nlist , self .bytesPerVec , self .bytesPerSubVec )
58
+ print ('FAISS train index' )
59
+ self .f_index .train (self .train_data )
60
+ print ('FAISS train index finished' )
31
61
32
- self .modelLoaded = self .saveModelToDisk (model_location , self .f_index )
62
+ # write index to disk
63
+ self .modelLoaded = self .saveModelToDisk (model_location , self .f_index )
33
64
self .is_initiated = self .modelLoaded
65
+
34
66
return self .is_initiated
35
67
36
68
def isInitiated (self ):
@@ -39,11 +71,11 @@ def isInitiated(self):
39
71
def loadModelFromDisk (self , location ):
40
72
try :
41
73
# read index
42
- self .f_index = read_index (location )
74
+ self .f_index = faiss . read_index (location )
43
75
print ('FAISS index loading success' )
44
76
return True
45
- except :
46
- print ('FAISS index loading failed' )
77
+ except Exception as e :
78
+ print ('FAISS index loading failed' , e )
47
79
return False
48
80
49
81
def saveModelToDisk (self , location , index ):
@@ -58,32 +90,62 @@ def saveModelToDisk(self, location, index):
58
90
59
91
def addVectors (self , documents ):
60
92
ids = []
61
- vecs = []
93
+ # add vectors
62
94
for document in documents :
63
- _id = document ._id
64
- vec = document .vector
65
- ids .append (_id )
66
- vector_e = vec .e
67
- vector_e_l = len (vector_e )
68
- # check if the vector length is below dimention limit
69
- # then pad vector with 0 by dimension
70
- if vector_e_l < self .dim :
71
- vector_e .extend ([0 ]* (self .dim - vector_e_l ))
72
- # make sure vector length doesn't exceed dimension limit
73
- vecs .append (vector_e [:self .dim ])
74
- # convert to np matrix
75
- vec_data = np .matrix (vecs ).astype ('float32' )
76
- id_data = np .array (ids ).astype ('int' )
77
- # add vector
78
- self .f_index .add_with_ids (vec_data , id_data )
95
+ # add document to queue
96
+ self .pipeline .put_nowait (document )
97
+ ids .append (document ._id )
79
98
return True , ids
80
99
100
+ def process (self ):
101
+ while (self .process_flag ):
102
+ # print(list(self.pipeline.queue))
103
+
104
+ # set a timeout till next vector indexing
105
+ time .sleep (self .process_timeout_sec )
106
+
107
+ # check if queue is not empty
108
+ if self .pipeline .qsize () > 0 :
109
+ ids = []
110
+ vecs = []
111
+
112
+ # fetch all currently available documents from queue
113
+ while not self .pipeline .empty ():
114
+ # extract document & contents
115
+ document = self .pipeline .get_nowait ()
116
+ _id = document ._id
117
+ vec = document .vector
118
+ ids .append (_id )
119
+ vector_e = vec .e
120
+ vector_e_l = len (vector_e )
121
+ # check if the vector length is below dimention limit
122
+ # then pad vector with 0 by dimension
123
+ if vector_e_l < self .dim :
124
+ vector_e .extend ([0 ]* (self .dim - vector_e_l ))
125
+ # make sure vector length doesn't exceed dimension limit
126
+ vecs .append (vector_e [:self .dim ])
127
+
128
+ # convert to np matrix
129
+ vec_data = np .matrix (vecs ).astype ('float32' )
130
+ id_data = np .array (ids ).astype ('int' )
131
+
132
+ # Lock index read / wtite until it is built
133
+ with self ._lock :
134
+ # add vector
135
+ self .f_index .add_with_ids (vec_data , id_data )
136
+
137
+ # write to disk
138
+ self .saveModelToDisk (model_location , self .f_index )
139
+
81
140
def deleteVectors (self , ids ):
82
141
83
142
return True , ids
84
143
85
144
def getNearest (self , matrix , k ):
86
145
# convert to np matrix
87
146
vec_data = np .matrix (matrix ).astype ('float32' )
88
- D , I = self .f_index .search (vec_data , k )
147
+
148
+ # Lock index read / wtite until nearest neighbor search
149
+ with self ._lock :
150
+ D , I = self .f_index .search (vec_data , k )
89
151
return True , I .tolist (), D .tolist ()
0 commit comments