bourdoiscatie commited on
Commit
a41e515
·
verified ·
1 Parent(s): 2d0b7f5

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +154 -0
app.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ import gradio as gr
3
+ from datasets import load_dataset
4
+ import pandas as pd
5
+ from sentence_transformers import SentenceTransformer
6
+ from sentence_transformers.quantization import quantize_embeddings
7
+ import faiss
8
+ from usearch.index import Index
9
+
10
+ # Load titles and texts
11
+ title_text_dataset = load_dataset("mixedbread-ai/wikipedia-data-en-2023-11", split="train", num_proc=4).select_columns(["title", "text"])
12
+
13
+ # Load the int8 and binary indices. Int8 is loaded as a view to save memory, as we never actually perform search with it.
14
+ int8_view = Index.restore("wikipedia_int8_usearch_50m.index", view=True)
15
+ binary_index: faiss.IndexBinaryFlat = faiss.read_index_binary("wikipedia_ubinary_faiss_50m.index")
16
+ binary_ivf: faiss.IndexBinaryIVF = faiss.read_index_binary("wikipedia_ubinary_ivf_faiss_50m.index")
17
+
18
+ # Load the SentenceTransformer model for embedding the queries
19
+ model = SentenceTransformer("bourdoiscatie/FAT5_cosinus")
20
+
21
+
22
+ def search(query, top_k: int = 20, rescore_multiplier: int = 1, use_approx: bool = False):
23
+ # 1. Embed the query as float32
24
+ start_time = time.time()
25
+ query_embedding = model.encode(query)
26
+ embed_time = time.time() - start_time
27
+
28
+ # 2. Quantize the query to ubinary
29
+ start_time = time.time()
30
+ query_embedding_ubinary = quantize_embeddings(query_embedding.reshape(1, -1), "ubinary")
31
+ quantize_time = time.time() - start_time
32
+
33
+ # 3. Search the binary index (either exact or approximate)
34
+ index = binary_ivf if use_approx else binary_index
35
+ start_time = time.time()
36
+ _scores, binary_ids = index.search(query_embedding_ubinary, top_k * rescore_multiplier)
37
+ binary_ids = binary_ids[0]
38
+ search_time = time.time() - start_time
39
+
40
+ # 4. Load the corresponding int8 embeddings
41
+ start_time = time.time()
42
+ int8_embeddings = int8_view[binary_ids].astype(int)
43
+ load_time = time.time() - start_time
44
+
45
+ # 5. Rescore the top_k * rescore_multiplier using the float32 query embedding and the int8 document embeddings
46
+ start_time = time.time()
47
+ scores = query_embedding @ int8_embeddings.T
48
+ rescore_time = time.time() - start_time
49
+
50
+ # 6. Sort the scores and return the top_k
51
+ start_time = time.time()
52
+ indices = scores.argsort()[::-1][:top_k]
53
+ top_k_indices = binary_ids[indices]
54
+ top_k_scores = scores[indices]
55
+ top_k_titles, top_k_texts = zip(
56
+ *[(title_text_dataset[idx]["title"], title_text_dataset[idx]["text"]) for idx in top_k_indices.tolist()]
57
+ )
58
+ df = pd.DataFrame({"Score": [round(value, 2) for value in top_k_scores], "Titre": top_k_titles, "Texte": top_k_texts})
59
+ sort_time = time.time() - start_time
60
+
61
+ return df, {
62
+ "Temps pour enchâsser la requête ": f"{embed_time:.4f} s",
63
+ "Temps pour la quantisation ": f"{quantize_time:.4f} s",
64
+ "Temps pour effectuer la recherche ": f"{search_time:.4f} s",
65
+ "Temps de chargement ": f"{load_time:.4f} s",
66
+ "Temps de rescorage ": f"{rescore_time:.4f} s",
67
+ "Temps pour trier les résustats ": f"{sort_time:.4f} s",
68
+ "Temps total pour la recherche ": f"{quantize_time + search_time + load_time + rescore_time + sort_time:.4f} s",
69
+ }
70
+
71
+
72
+ with gr.Blocks(title="Requêter Wikipedia en temps réel") as demo:
73
+ gr.Markdown(
74
+ """
75
+ ## Requêter Wikipedia en temps réel
76
+
77
+ Effectuer une requête dans un corpus composé de XX millions de paragraphes tirés d'articles de Wikipédia !
78
+ Les résultats sont renvoyés en temps réel via une architecture tournant sur un CPU 🚀
79
+
80
+
81
+ <details><summary>Détails du processus</summary>
82
+
83
+ Détails :
84
+ 1. La requête est enchâssée en float32 à l'aide du modèle [`CATIE-AQ/La_moelle`](https://huggingface.co/CATIE-AQ/La_moelle).
85
+ 2. La requête est quantizée en binaire à l'aide de la fonction `quantize_embeddings` de la bibliothèque [SentenceTransformers](https://sbert.net/).
86
+ 3. Un index binaire (XXM d'enchâssements binaires pesant XXGB de mémoire/espace disque) est requêté (en binaire si l'option approximatie sélectionnée, en int8 si l'option exacte est sélectionnée).
87
+ 4. Les n textes demandés par l'utilisateur jugés les plus pertinents sont chargés à la volée à partir d'un index int8 sur disque (XXM d'enchâssements int8 ; 0 bytes de mémoire, XXGB d'espace disque).
88
+ 5. Les n textes sont rescorés en utilisant la requête en float32 et les enchâssements en int8.
89
+ 6. Les n premiers textes sont triés par score et affichés.
90
+
91
+ This process is designed to be memory efficient and fast, with the binary index being small enough to fit in memory and the int8 index being loaded as a view to save memory.
92
+ In total, this process requires keeping 1) the model in memory, 2) the binary index in memory, and 3) the int8 index on disk. With a dimensionality of 1024,
93
+ we need `1024 / 8 * num_docs` bytes for the binary index and `1024 * num_docs` bytes for the int8 index.
94
+
95
+ This is notably cheaper than doing the same process with float32 embeddings, which would require `4 * 1024 * num_docs` bytes of memory/disk space for the float32 index, i.e. 32x as much memory and 4x as much disk space.
96
+ Additionally, the binary index is much faster (up to 32x) to search than the float32 index, while the rescoring is also extremely efficient. In conclusion, this process allows for fast, scalable, cheap, and memory-efficient retrieval.
97
+
98
+ Ce processus est conçu pour être rapide et efficace en termes de mémoire : l'index binaire étant suffisamment petit pour tenir dans la mémoire et l'index int8 étant chargé en tant que vue pour économiser de la mémoire.
99
+ Au total, ce processus nécessite de conserver 1) le modèle en mémoire, 2) l'index binaire en mémoire et 3) l'index int8 sur le disque.
100
+ Avec une dimension de 1024, nous avons besoin de `1024 / 8 * num_docs` octets pour l'index binaire et de `1024 * num_docs` octets pour l'index int8.
101
+
102
+ C'est nettement moins cher que de faire le même processus avec des enchâssements en float32 qui nécessiterait `4 * 1024 * num_docs` octets de mémoire/espace disque pour l'index float32, soit 32x plus de mémoire et 4x plus d'espace disque.
103
+ De plus, l'index binaire est beaucoup plus rapide (jusqu'à 32x) à rechercher que l'index float32, tandis que le rescorage est également extrêmement efficace.
104
+ En conclusion, ce processus permet une recherche rapide, évolutive, peu coûteuse et efficace en termes de mémoire.
105
+
106
+ </details>
107
+ """
108
+ )
109
+ with gr.Row():
110
+ with gr.Column(scale=75):
111
+ query = gr.Textbox(
112
+ label="Recherche d'articles dans le Wikipédia francophone",
113
+ placeholder="Saisissez une requête pour rechercher des textes pertinents dans Wikipédia.",
114
+ )
115
+ with gr.Column(scale=25):
116
+ use_approx = gr.Radio(
117
+ choices=[("Recherche exacte", False), ("Recherche approximative", True)],
118
+ value=True,
119
+ label="Index de recherche",
120
+ )
121
+
122
+ with gr.Row():
123
+ with gr.Column(scale=2):
124
+ top_k = gr.Slider(
125
+ minimum=10,
126
+ maximum=200,
127
+ step=5,
128
+ value=20,
129
+ label="Nombre de documents à rechercher",
130
+ info="Recherche effectué via un bi-encodeur binaire",
131
+ )
132
+ with gr.Column(scale=2):
133
+ rescore_multiplier = gr.Slider(
134
+ minimum=1,
135
+ maximum=10,
136
+ step=1,
137
+ value=1,
138
+ label="Coefficient de rescorage",
139
+ info="Reranking via le coefficient`",
140
+ )
141
+
142
+ search_button = gr.Button(value="Search")
143
+
144
+ with gr.Row():
145
+ with gr.Column(scale=4):
146
+ output = gr.Dataframe(headers=["Score", "Titre", "Texte"])
147
+ with gr.Column(scale=1):
148
+ json = gr.JSON()
149
+
150
+ query.submit(search, inputs=[query, top_k, rescore_multiplier, use_approx], outputs=[output, json])
151
+ search_button.click(search, inputs=[query, top_k, rescore_multiplier, use_approx], outputs=[output, json])
152
+
153
+ demo.queue()
154
+ demo.launch()