flashrag-dev 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. flashrag/__init__.py +0 -0
  2. flashrag/config/__init__.py +2 -0
  3. flashrag/config/config.py +219 -0
  4. flashrag/dataset/__init__.py +2 -0
  5. flashrag/dataset/dataset.py +160 -0
  6. flashrag/dataset/utils.py +60 -0
  7. flashrag/evaluator/__init__.py +2 -0
  8. flashrag/evaluator/_bleu.py +209 -0
  9. flashrag/evaluator/evaluator.py +82 -0
  10. flashrag/evaluator/metrics.py +508 -0
  11. flashrag/evaluator/utils.py +19 -0
  12. flashrag/generator/__init__.py +2 -0
  13. flashrag/generator/fid.py +247 -0
  14. flashrag/generator/generator.py +623 -0
  15. flashrag/generator/openai_generator.py +96 -0
  16. flashrag/generator/stop_word_criteria.py +105 -0
  17. flashrag/judger/__init__.py +1 -0
  18. flashrag/judger/judger.py +184 -0
  19. flashrag/pipeline/__init__.py +3 -0
  20. flashrag/pipeline/active_pipeline.py +980 -0
  21. flashrag/pipeline/branching_pipeline.py +250 -0
  22. flashrag/pipeline/pipeline.py +248 -0
  23. flashrag/pipeline/replug_utils.py +249 -0
  24. flashrag/prompt/__init__.py +1 -0
  25. flashrag/prompt/base_prompt.py +165 -0
  26. flashrag/prompt/selfask_examplars.py +108 -0
  27. flashrag/prompt/trace_examplars.py +4015 -0
  28. flashrag/refiner/__init__.py +2 -0
  29. flashrag/refiner/kg_refiner.py +612 -0
  30. flashrag/refiner/llmlingua_compressor.py +2360 -0
  31. flashrag/refiner/refiner.py +252 -0
  32. flashrag/refiner/selective_context_compressor.py +294 -0
  33. flashrag/retriever/__init__.py +3 -0
  34. flashrag/retriever/__main__.py +4 -0
  35. flashrag/retriever/encoder.py +120 -0
  36. flashrag/retriever/index_builder.py +334 -0
  37. flashrag/retriever/reranker.py +145 -0
  38. flashrag/retriever/retriever.py +346 -0
  39. flashrag/retriever/utils.py +49 -0
  40. flashrag/utils/__init__.py +2 -0
  41. flashrag/utils/constants.py +51 -0
  42. flashrag/utils/pred_parse.py +25 -0
  43. flashrag/utils/utils.py +135 -0
  44. flashrag_dev-0.1.1.dist-info/LICENSE +21 -0
  45. flashrag_dev-0.1.1.dist-info/METADATA +616 -0
  46. flashrag_dev-0.1.1.dist-info/RECORD +48 -0
  47. flashrag_dev-0.1.1.dist-info/WHEEL +5 -0
  48. flashrag_dev-0.1.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,334 @@
1
+ import os
2
+ import faiss
3
+ import json
4
+ import warnings
5
+ import numpy as np
6
+ from typing import cast
7
+ import shutil
8
+ import subprocess
9
+ import argparse
10
+ import datasets
11
+ import torch
12
+ from tqdm import tqdm
13
+ from flashrag.retriever.utils import load_model, load_corpus, pooling
14
+
15
+
16
+ class Index_Builder:
17
+ r"""A tool class used to build an index used in retrieval."""
18
+
19
+ def __init__(
20
+ self,
21
+ retrieval_method,
22
+ model_path,
23
+ corpus_path,
24
+ save_dir,
25
+ max_length,
26
+ batch_size,
27
+ use_fp16,
28
+ pooling_method,
29
+ faiss_type=None,
30
+ embedding_path=None,
31
+ save_embedding=False,
32
+ faiss_gpu=False,
33
+ use_sentence_transformer=False,
34
+ bm25_backend='bm25s'
35
+ ):
36
+
37
+ self.retrieval_method = retrieval_method.lower()
38
+ self.model_path = model_path
39
+ self.corpus_path = corpus_path
40
+ self.save_dir = save_dir
41
+ self.max_length = max_length
42
+ self.batch_size = batch_size
43
+ self.use_fp16 = use_fp16
44
+ self.pooling_method = pooling_method
45
+ self.faiss_type = faiss_type if faiss_type is not None else "Flat"
46
+ self.embedding_path = embedding_path
47
+ self.save_embedding = save_embedding
48
+ self.faiss_gpu = faiss_gpu
49
+ self.use_sentence_transformer = use_sentence_transformer
50
+ self.bm25_backend = bm25_backend
51
+
52
+ self.gpu_num = torch.cuda.device_count()
53
+ # prepare save dir
54
+ print(self.save_dir)
55
+ if not os.path.exists(self.save_dir):
56
+ os.makedirs(self.save_dir)
57
+ else:
58
+ if not self._check_dir(self.save_dir):
59
+ warnings.warn("Some files already exists in save dir and may be overwritten.", UserWarning)
60
+
61
+ self.index_save_path = os.path.join(self.save_dir, f"{self.retrieval_method}_{self.faiss_type}.index")
62
+
63
+ self.embedding_save_path = os.path.join(self.save_dir, f"emb_{self.retrieval_method}.memmap")
64
+
65
+ self.corpus = load_corpus(self.corpus_path)
66
+
67
+ print("Finish loading...")
68
+
69
+ @staticmethod
70
+ def _check_dir(dir_path):
71
+ r"""Check if the dir path exists and if there is content."""
72
+
73
+ if os.path.isdir(dir_path):
74
+ if len(os.listdir(dir_path)) > 0:
75
+ return False
76
+ else:
77
+ os.makedirs(dir_path, exist_ok=True)
78
+ return True
79
+
80
+ def build_index(self):
81
+ r"""Constructing different indexes based on selective retrieval method."""
82
+ if self.retrieval_method == "bm25":
83
+ if self.bm25_backend == 'pyserini':
84
+ self.build_bm25_index_pyserini()
85
+ elif self.bm25_backend == 'bm25s':
86
+ self.build_bm25_index_bm25s()
87
+ else:
88
+ assert False, "Invalid bm25 backend!"
89
+ else:
90
+ self.build_dense_index()
91
+
92
+ def build_bm25_index_pyserini(self):
93
+ """Building BM25 index based on Pyserini library.
94
+
95
+ Reference: https://github.com/castorini/pyserini/blob/master/docs/usage-index.md#building-a-bm25-index-direct-java-implementation
96
+ """
97
+
98
+ # to use pyserini pipeline, we first need to place jsonl file in the folder
99
+ self.save_dir = os.path.join(self.save_dir, "bm25")
100
+ os.makedirs(self.save_dir, exist_ok=True)
101
+ temp_dir = self.save_dir + "/temp"
102
+ temp_file_path = temp_dir + "/temp.jsonl"
103
+ os.makedirs(temp_dir)
104
+ shutil.copyfile(self.corpus_path, temp_file_path)
105
+
106
+ print("Start building bm25 index...")
107
+ pyserini_args = [
108
+ "--collection",
109
+ "JsonCollection",
110
+ "--input",
111
+ temp_dir,
112
+ "--index",
113
+ self.save_dir,
114
+ "--generator",
115
+ "DefaultLuceneDocumentGenerator",
116
+ "--threads",
117
+ "1",
118
+ ]
119
+
120
+ subprocess.run(["python", "-m", "pyserini.index.lucene"] + pyserini_args)
121
+
122
+ shutil.rmtree(temp_dir)
123
+
124
+ print("Finish!")
125
+
126
+ def build_bm25_index_bm25s(self):
127
+ """Building BM25 index based on bm25s library."""
128
+
129
+ import bm25s
130
+
131
+ self.save_dir = os.path.join(self.save_dir, 'bm25')
132
+ os.makedirs(self.save_dir, exist_ok=True)
133
+
134
+ corpus = datasets.load_dataset("json", data_files=self.corpus_path, split="train")
135
+ corpus_text = corpus['contents']
136
+ retriever = bm25s.BM25(corpus=corpus, backend='numba')
137
+ retriever.index(corpus_text)
138
+ retriever.save(self.save_dir,corpus=corpus)
139
+
140
+ print("Finish!")
141
+
142
+
143
+ def _load_embedding(self, embedding_path, corpus_size, hidden_size):
144
+ all_embeddings = np.memmap(embedding_path, mode="r", dtype=np.float32).reshape(corpus_size, hidden_size)
145
+ return all_embeddings
146
+
147
+ def _save_embedding(self, all_embeddings):
148
+ memmap = np.memmap(self.embedding_save_path, shape=all_embeddings.shape, mode="w+", dtype=all_embeddings.dtype)
149
+ length = all_embeddings.shape[0]
150
+ # add in batch
151
+ save_batch_size = 10000
152
+ if length > save_batch_size:
153
+ for i in tqdm(range(0, length, save_batch_size), leave=False, desc="Saving Embeddings"):
154
+ j = min(i + save_batch_size, length)
155
+ memmap[i:j] = all_embeddings[i:j]
156
+ else:
157
+ memmap[:] = all_embeddings
158
+
159
+ def st_encode_all(self):
160
+ if self.gpu_num > 1:
161
+ print("Use multi gpu!")
162
+ self.batch_size = self.batch_size * self.gpu_num
163
+
164
+ sentence_list = [item["contents"] for item in self.corpus]
165
+ if self.retrieval_method == "e5":
166
+ sentence_list = [f"passage: {doc}" for doc in sentence_list]
167
+ all_embeddings = self.encoder.encode(sentence_list, batch_size=self.batch_size)
168
+
169
+ return all_embeddings
170
+
171
+ def encode_all(self):
172
+ if self.gpu_num > 1:
173
+ print("Use multi gpu!")
174
+ self.encoder = torch.nn.DataParallel(self.encoder)
175
+ self.batch_size = self.batch_size * self.gpu_num
176
+
177
+ all_embeddings = []
178
+
179
+ for start_idx in tqdm(range(0, len(self.corpus), self.batch_size), desc="Inference Embeddings:"):
180
+ batch_data = self.corpus[start_idx : start_idx + self.batch_size]["contents"]
181
+
182
+ if self.retrieval_method == "e5":
183
+ batch_data = [f"passage: {doc}" for doc in batch_data]
184
+
185
+ inputs = self.tokenizer(
186
+ batch_data,
187
+ padding=True,
188
+ truncation=True,
189
+ return_tensors="pt",
190
+ max_length=self.max_length,
191
+ ).to("cuda")
192
+
193
+ inputs = {k: v.cuda() for k, v in inputs.items()}
194
+
195
+ # TODO: support encoder-only T5 model
196
+ if "T5" in type(self.encoder).__name__ or (self.gpu_num > 1 and "T5" in type(self.encoder.module).__name__):
197
+ # T5-based retrieval model
198
+ decoder_input_ids = torch.zeros((inputs["input_ids"].shape[0], 1), dtype=torch.long).to(
199
+ inputs["input_ids"].device
200
+ )
201
+ output = self.encoder(**inputs, decoder_input_ids=decoder_input_ids, return_dict=True)
202
+ embeddings = output.last_hidden_state[:, 0, :]
203
+
204
+ else:
205
+ output = self.encoder(**inputs, return_dict=True)
206
+ embeddings = pooling(
207
+ output.pooler_output, output.last_hidden_state, inputs["attention_mask"], self.pooling_method
208
+ )
209
+ if "dpr" not in self.retrieval_method:
210
+ embeddings = torch.nn.functional.normalize(embeddings, dim=-1)
211
+
212
+ embeddings = cast(torch.Tensor, embeddings)
213
+ embeddings = embeddings.detach().cpu().numpy()
214
+ all_embeddings.append(embeddings)
215
+
216
+ all_embeddings = np.concatenate(all_embeddings, axis=0)
217
+ all_embeddings = all_embeddings.astype(np.float32)
218
+
219
+ return all_embeddings
220
+
221
+ @torch.no_grad()
222
+ def build_dense_index(self):
223
+ """Obtain the representation of documents based on the embedding model(BERT-based) and
224
+ construct a faiss index.
225
+ """
226
+
227
+ if os.path.exists(self.index_save_path):
228
+ print("The index file already exists and will be overwritten.")
229
+
230
+ if self.use_sentence_transformer:
231
+ from flashrag.retriever.encoder import STEncoder
232
+
233
+ self.encoder = STEncoder(
234
+ model_name=self.retrieval_method,
235
+ model_path=self.model_path,
236
+ max_length=self.max_length,
237
+ use_fp16=self.use_fp16,
238
+ )
239
+ hidden_size = self.encoder.model.get_sentence_embedding_dimension()
240
+ else:
241
+ self.encoder, self.tokenizer = load_model(model_path=self.model_path, use_fp16=self.use_fp16)
242
+ hidden_size = self.encoder.config.hidden_size
243
+
244
+ if self.embedding_path is not None:
245
+ corpus_size = len(self.corpus)
246
+ all_embeddings = self._load_embedding(self.embedding_path, corpus_size, hidden_size)
247
+ else:
248
+ all_embeddings = self.st_encode_all() if self.use_sentence_transformer else self.encode_all()
249
+ if self.save_embedding:
250
+ self._save_embedding(all_embeddings)
251
+ del self.corpus
252
+
253
+ # build index
254
+ print("Creating index")
255
+ dim = all_embeddings.shape[-1]
256
+ faiss_index = faiss.index_factory(dim, self.faiss_type, faiss.METRIC_INNER_PRODUCT)
257
+
258
+ if self.faiss_gpu:
259
+ co = faiss.GpuMultipleClonerOptions()
260
+ co.useFloat16 = True
261
+ co.shard = True
262
+ faiss_index = faiss.index_cpu_to_all_gpus(faiss_index, co)
263
+ if not faiss_index.is_trained:
264
+ faiss_index.train(all_embeddings)
265
+ faiss_index.add(all_embeddings)
266
+ faiss_index = faiss.index_gpu_to_cpu(faiss_index)
267
+ else:
268
+ if not faiss_index.is_trained:
269
+ faiss_index.train(all_embeddings)
270
+ faiss_index.add(all_embeddings)
271
+
272
+ faiss.write_index(faiss_index, self.index_save_path)
273
+ print("Finish!")
274
+
275
+
276
+ MODEL2POOLING = {"e5": "mean", "bge": "cls", "contriever": "mean", "jina": "mean"}
277
+
278
+
279
+ def main():
280
+ parser = argparse.ArgumentParser(description="Creating index.")
281
+
282
+ # Basic parameters
283
+ parser.add_argument("--retrieval_method", type=str)
284
+ parser.add_argument("--model_path", type=str, default=None)
285
+ parser.add_argument("--corpus_path", type=str)
286
+ parser.add_argument("--save_dir", default="indexes/", type=str)
287
+
288
+ # Parameters for building dense index
289
+ parser.add_argument("--max_length", type=int, default=180)
290
+ parser.add_argument("--batch_size", type=int, default=512)
291
+ parser.add_argument("--use_fp16", default=False, action="store_true")
292
+ parser.add_argument("--pooling_method", type=str, default=None)
293
+ parser.add_argument("--faiss_type", default=None, type=str)
294
+ parser.add_argument("--embedding_path", default=None, type=str)
295
+ parser.add_argument("--save_embedding", action="store_true", default=False)
296
+ parser.add_argument("--faiss_gpu", default=False, action="store_true")
297
+ parser.add_argument("--sentence_transformer", action="store_true", default=False)
298
+ parser.add_argument("--bm25_backend", default='bm25s', choices=['bm25s','pyserini'])
299
+
300
+ args = parser.parse_args()
301
+
302
+ if args.pooling_method is None:
303
+ pooling_method = "mean"
304
+ for k, v in MODEL2POOLING.items():
305
+ if k in args.retrieval_method.lower():
306
+ pooling_method = v
307
+ break
308
+ else:
309
+ if args.pooling_method not in ["mean", "cls", "pooler"]:
310
+ raise NotImplementedError
311
+ else:
312
+ pooling_method = args.pooling_method
313
+
314
+ index_builder = Index_Builder(
315
+ retrieval_method=args.retrieval_method,
316
+ model_path=args.model_path,
317
+ corpus_path=args.corpus_path,
318
+ save_dir=args.save_dir,
319
+ max_length=args.max_length,
320
+ batch_size=args.batch_size,
321
+ use_fp16=args.use_fp16,
322
+ pooling_method=pooling_method,
323
+ faiss_type=args.faiss_type,
324
+ embedding_path=args.embedding_path,
325
+ save_embedding=args.save_embedding,
326
+ faiss_gpu=args.faiss_gpu,
327
+ use_sentence_transformer=args.sentence_transformer,
328
+ bm25_backend=args.bm25_backend
329
+ )
330
+ index_builder.build_index()
331
+
332
+
333
+ if __name__ == "__main__":
334
+ main()
@@ -0,0 +1,145 @@
1
+ from typing import List
2
+ import torch
3
+ import warnings
4
+ import numpy as np
5
+ from tqdm import tqdm
6
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
7
+ from flashrag.retriever.encoder import Encoder
8
+
9
+
10
+ class BaseReranker:
11
+ r"""Base object for all rerankers."""
12
+
13
+ def __init__(self, config):
14
+ self.config = config
15
+ self.reranker_model_name = config["rerank_model_name"]
16
+ self.reranker_model_path = config["rerank_model_path"]
17
+ self.topk = config["rerank_topk"]
18
+ self.max_length = config["rerank_max_length"]
19
+ self.batch_size = config["rerank_batch_size"]
20
+ self.device = config["device"]
21
+
22
+ def get_rerank_scores(self, query_list: List[str], doc_list: List[str], batch_size):
23
+ """Return flatten list of scores for each (query,doc) pair
24
+ Args:
25
+ query_list: List of N queries
26
+ doc_list: Nested list of length N, each element corresponds to K documents of a query
27
+
28
+ Return:
29
+ [score(q1,d1), score(q1,d2),... score(q2,d1),...]
30
+ """
31
+ all_scores = []
32
+ return all_scores
33
+
34
+ @torch.inference_mode(mode=True)
35
+ def rerank(self, query_list, doc_list, batch_size=None, topk=None):
36
+ r"""Rerank doc_list."""
37
+ if batch_size is None:
38
+ batch_size = self.batch_size
39
+ if topk is None:
40
+ topk = self.topk
41
+ if isinstance(query_list, str):
42
+ query_list = [query_list]
43
+ if not isinstance(doc_list[0], list):
44
+ doc_list = [doc_list]
45
+
46
+ assert len(query_list) == len(doc_list)
47
+ if topk < min([len(docs) for docs in doc_list]):
48
+ warnings.warn("The number of doc returned by the retriever is less than the topk.")
49
+
50
+ # get doc contents
51
+ doc_contents = []
52
+ for docs in doc_list:
53
+ if all([isinstance(doc, str) for doc in docs]):
54
+ doc_contents.append([doc for doc in docs])
55
+ else:
56
+ doc_contents.append([doc["contents"] for doc in docs])
57
+
58
+ all_scores = self.get_rerank_scores(query_list, doc_contents, batch_size)
59
+ assert len(all_scores) == sum([len(docs) for docs in doc_list])
60
+
61
+ # sort docs
62
+ start_idx = 0
63
+ final_scores = []
64
+ final_docs = []
65
+ for docs in doc_list:
66
+ doc_scores = all_scores[start_idx : start_idx + len(docs)]
67
+ doc_scores = [float(score) for score in doc_scores]
68
+ sort_idxs = np.argsort(doc_scores)[::-1][:topk]
69
+ start_idx += len(docs)
70
+
71
+ final_docs.append([docs[idx] for idx in sort_idxs])
72
+ final_scores.append([doc_scores[idx] for idx in sort_idxs])
73
+
74
+ return final_docs, final_scores
75
+
76
+
77
+ class CrossReranker(BaseReranker):
78
+ def __init__(self, config):
79
+ super().__init__(config)
80
+ self.tokenizer = AutoTokenizer.from_pretrained(self.reranker_model_path)
81
+ self.ranker = AutoModelForSequenceClassification.from_pretrained(self.reranker_model_path, num_labels=1)
82
+ self.ranker.eval()
83
+ self.ranker.to(self.device)
84
+
85
+ @torch.inference_mode(mode=True)
86
+ def get_rerank_scores(self, query_list, doc_list, batch_size):
87
+ # flatten all pairs
88
+ all_pairs = []
89
+ for query, docs in zip(query_list, doc_list):
90
+ all_pairs.extend([[query, doc] for doc in docs])
91
+ all_scores = []
92
+ for start_idx in tqdm(range(0, len(all_pairs), batch_size), desc="Reranking process: "):
93
+ pair_batch = all_pairs[start_idx : start_idx + batch_size]
94
+
95
+ inputs = self.tokenizer(
96
+ pair_batch, padding=True, truncation=True, return_tensors="pt", max_length=self.max_length
97
+ ).to(self.device)
98
+ batch_scores = (
99
+ self.ranker(**inputs, return_dict=True)
100
+ .logits.view(
101
+ -1,
102
+ )
103
+ .float()
104
+ .cpu()
105
+ )
106
+ all_scores.extend(batch_scores)
107
+
108
+ return all_scores
109
+
110
+
111
+ class BiReranker(BaseReranker):
112
+ def __init__(self, config):
113
+ super().__init__(config)
114
+ self.encoder = Encoder(
115
+ model_name=self.reranker_model_name,
116
+ model_path=self.reranker_model_path,
117
+ pooling_method=config["rerank_pooling_method"],
118
+ max_length=self.max_length,
119
+ use_fp16=config["rerank_use_fp16"],
120
+ )
121
+
122
+ def get_rerank_scores(self, query_list, doc_list, batch_size):
123
+ query_emb = []
124
+ for start_idx in range(0, len(query_list), batch_size):
125
+ query_batch = query_list[start_idx : start_idx + batch_size]
126
+ batch_emb = self.encoder.encode(query_batch, is_query=True)
127
+ query_emb.append(batch_emb)
128
+ query_emb = np.concatenate(query_emb, axis=0)
129
+
130
+ flat_doc_list = sum(doc_list, [])
131
+ doc_emb = []
132
+ for start_idx in range(0, len(flat_doc_list), batch_size):
133
+ doc_batch = flat_doc_list[start_idx : start_idx + batch_size]
134
+ batch_emb = self.encoder.encode(doc_batch, is_query=False)
135
+ doc_emb.append(batch_emb)
136
+ doc_emb = np.concatenate(doc_emb, axis=0)
137
+
138
+ scores = query_emb @ doc_emb.T # K*L
139
+ all_scores = []
140
+ score_idx = 0
141
+ for idx, doc in enumerate(doc_list):
142
+ all_scores.extend(scores[idx, score_idx : score_idx + len(doc)])
143
+ score_idx += len(doc)
144
+
145
+ return all_scores