flashrag-dev 0.1.4.dev20250623__tar.gz → 0.1.4.dev20250720__tar.gz
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.
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/PKG-INFO +27 -1
- flashrag_dev-0.1.4.dev20250623/flashrag_dev.egg-info/PKG-INFO → flashrag_dev-0.1.4.dev20250720/README.md +26 -17
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/config/basic_config.yaml +4 -1
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/retriever/encoder.py +1 -1
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/retriever/index_builder.py +296 -27
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/retriever/retriever.py +207 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/utils/utils.py +4 -2
- flashrag_dev-0.1.4.dev20250720/flashrag/version.py +1 -0
- flashrag_dev-0.1.4.dev20250623/README.md → flashrag_dev-0.1.4.dev20250720/flashrag_dev.egg-info/PKG-INFO +43 -0
- flashrag_dev-0.1.4.dev20250623/flashrag/version.py +0 -1
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/LICENSE +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/__init__.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/config/__init__.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/config/config.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/dataset/__init__.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/dataset/dataset.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/dataset/utils.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/evaluator/__init__.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/evaluator/_bleu.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/evaluator/evaluator.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/evaluator/metrics.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/evaluator/utils.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/generator/__init__.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/generator/fid.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/generator/generator.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/generator/multimodal_generator.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/generator/openai_generator.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/generator/stop_word_criteria.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/generator/utils.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/judger/__init__.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/judger/judger.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/pipeline/__init__.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/pipeline/active_pipeline.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/pipeline/branching_pipeline.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/pipeline/mm_pipeline.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/pipeline/pipeline.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/pipeline/reasoning_pipeline.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/pipeline/replug_utils.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/prompt/__init__.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/prompt/base_prompt.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/prompt/mm_prompt.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/prompt/selfask_examplars.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/prompt/trace_examplars.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/refiner/__init__.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/refiner/kg_refiner.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/refiner/llmlingua_compressor.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/refiner/refiner.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/refiner/selective_context_compressor.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/retriever/__init__.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/retriever/__main__.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/retriever/reranker.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/retriever/utils.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/utils/__init__.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/utils/constants.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/utils/pred_parse.py +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag_dev.egg-info/SOURCES.txt +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag_dev.egg-info/dependency_links.txt +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag_dev.egg-info/requires.txt +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag_dev.egg-info/top_level.txt +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/pyproject.toml +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/setup.cfg +0 -0
- {flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flashrag_dev
|
|
3
|
-
Version: 0.1.4.
|
|
3
|
+
Version: 0.1.4.dev20250720
|
|
4
4
|
Summary: A library for efficient Retrieval-Augmented Generation research
|
|
5
5
|
Home-page: https://github.com/RUC-NLPIR/FlashRAG
|
|
6
6
|
Author: Jiajie Jin, Yutao Zhu, Chenghao Zhang, Xinyu Yang, Zhicheng Dou
|
|
@@ -278,6 +278,32 @@ python -m flashrag.retriever.index_builder \
|
|
|
278
278
|
--save_dir indexes/
|
|
279
279
|
```
|
|
280
280
|
|
|
281
|
+
### For Sparse Neural Retrieval Methods (SPLADE)
|
|
282
|
+
|
|
283
|
+
##### Install Seismic Index:
|
|
284
|
+
```bash
|
|
285
|
+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Install Rust for compiling
|
|
286
|
+
pip install pyseismic-lsr # Install Seismic
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
##### Then build the index with Seismic:
|
|
290
|
+
```bash
|
|
291
|
+
python -m flashrag.retriever.index_builder \ # builder
|
|
292
|
+
--retrieval_method splade \ # Model name to trigger seismic index (splade only available)
|
|
293
|
+
--model_path retriever/splade-v3 \ # Local path or repository path are both supported.
|
|
294
|
+
--corpus_embedded_path data/ms_marco/ms_marco_embedded_corpus.jsonl \ # Use cached embedded corpus if corpus is already available in seismic expected format
|
|
295
|
+
--corpus_path data/ms_marco/ms_marco_corpus.jsonl \ # Corpus path in format {id, contents} jsonl file to be embedded if not already built
|
|
296
|
+
--save_dir indexes/ \ # save index directory
|
|
297
|
+
--use_fp16 \ # tell to use fp16 for splade model
|
|
298
|
+
--max_length 512 \ # max tokens for each document
|
|
299
|
+
--batch_size 4 \ # batch size for splade model (4-5 seems the best size for Tesla T4 16GB)
|
|
300
|
+
--n_postings 1000 \ # seismic number of posting lists
|
|
301
|
+
--centroid_fraction 0.2 \ # seismic centroids
|
|
302
|
+
--min_cluster_size 2 \ # seismic min cluster
|
|
303
|
+
--summary_energy 0.4 \ # seismic energy
|
|
304
|
+
--batched_indexing 10000000 # seismic batch
|
|
305
|
+
--nknn 32 # Optional parameter. Tell to seismic to use also knn graph. if not present seismic will work without knn graph
|
|
306
|
+
```
|
|
281
307
|
### Using the ready-made pipeline
|
|
282
308
|
|
|
283
309
|
You can use the pipeline class we have already built (as shown in [<u>pipelines</u>](#pipelines)) to implement the RAG process inside. In this case, you just need to configure the config and load the corresponding pipeline.
|
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: flashrag-dev
|
|
3
|
-
Version: 0.1.4.dev20250623
|
|
4
|
-
Summary: A library for efficient Retrieval-Augmented Generation research
|
|
5
|
-
Home-page: https://github.com/RUC-NLPIR/FlashRAG
|
|
6
|
-
Author: Jiajie Jin, Yutao Zhu, Chenghao Zhang, Xinyu Yang, Zhicheng Dou
|
|
7
|
-
Author-email: jinjiajie@ruc.edu.cn
|
|
8
|
-
License: MIT License
|
|
9
|
-
Requires-Python: >=3.9
|
|
10
|
-
Description-Content-Type: text/markdown
|
|
11
|
-
Provides-Extra: core
|
|
12
|
-
Provides-Extra: retriever
|
|
13
|
-
Provides-Extra: generator
|
|
14
|
-
Provides-Extra: multimodal
|
|
15
|
-
Provides-Extra: full
|
|
16
|
-
License-File: LICENSE
|
|
17
|
-
|
|
18
1
|
# <div align="center">⚡FlashRAG: A Python Toolkit for Efficient RAG Research<div>
|
|
19
2
|
\[ English | [中文](README_zh.md) \]
|
|
20
3
|
<div align="center">
|
|
@@ -278,6 +261,32 @@ python -m flashrag.retriever.index_builder \
|
|
|
278
261
|
--save_dir indexes/
|
|
279
262
|
```
|
|
280
263
|
|
|
264
|
+
### For Sparse Neural Retrieval Methods (SPLADE)
|
|
265
|
+
|
|
266
|
+
##### Install Seismic Index:
|
|
267
|
+
```bash
|
|
268
|
+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Install Rust for compiling
|
|
269
|
+
pip install pyseismic-lsr # Install Seismic
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
##### Then build the index with Seismic:
|
|
273
|
+
```bash
|
|
274
|
+
python -m flashrag.retriever.index_builder \ # builder
|
|
275
|
+
--retrieval_method splade \ # Model name to trigger seismic index (splade only available)
|
|
276
|
+
--model_path retriever/splade-v3 \ # Local path or repository path are both supported.
|
|
277
|
+
--corpus_embedded_path data/ms_marco/ms_marco_embedded_corpus.jsonl \ # Use cached embedded corpus if corpus is already available in seismic expected format
|
|
278
|
+
--corpus_path data/ms_marco/ms_marco_corpus.jsonl \ # Corpus path in format {id, contents} jsonl file to be embedded if not already built
|
|
279
|
+
--save_dir indexes/ \ # save index directory
|
|
280
|
+
--use_fp16 \ # tell to use fp16 for splade model
|
|
281
|
+
--max_length 512 \ # max tokens for each document
|
|
282
|
+
--batch_size 4 \ # batch size for splade model (4-5 seems the best size for Tesla T4 16GB)
|
|
283
|
+
--n_postings 1000 \ # seismic number of posting lists
|
|
284
|
+
--centroid_fraction 0.2 \ # seismic centroids
|
|
285
|
+
--min_cluster_size 2 \ # seismic min cluster
|
|
286
|
+
--summary_energy 0.4 \ # seismic energy
|
|
287
|
+
--batched_indexing 10000000 # seismic batch
|
|
288
|
+
--nknn 32 # Optional parameter. Tell to seismic to use also knn graph. if not present seismic will work without knn graph
|
|
289
|
+
```
|
|
281
290
|
### Using the ready-made pipeline
|
|
282
291
|
|
|
283
292
|
You can use the pipeline class we have already built (as shown in [<u>pipelines</u>](#pipelines)) to implement the RAG process inside. In this case, you just need to configure the config and load the corresponding pipeline.
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/config/basic_config.yaml
RENAMED
|
@@ -68,7 +68,9 @@ bm25_backend: bm25s # pyserini, bm25s
|
|
|
68
68
|
use_sentence_transformer: False
|
|
69
69
|
silent_retrieval: True # whether to silent the retrieval process
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
seismic_query_cut: 10 # parameters for seismic. See seismic paper for full details
|
|
72
|
+
seismic_heap_factor: 0.8 # parameters for seismic. See seismic paper for full details
|
|
73
|
+
# -------------------------------------------------Reranker Settings------------------------------------------------#
|
|
72
74
|
use_reranker: False # whether to use reranker
|
|
73
75
|
rerank_model_name: ~ # same as retrieval_method
|
|
74
76
|
rerank_model_path: ~ # path to reranker model, path will be automatically find in `model2path`
|
|
@@ -117,6 +119,7 @@ gpu_memory_utilization: 0.85 # ratio of gpu's memory usage for generator
|
|
|
117
119
|
# -------------------------------------------------Evaluation Settings------------------------------------------------#
|
|
118
120
|
# Metrics to evaluate the result
|
|
119
121
|
metrics: ["em", "f1", "acc", "precision", "recall", "input_tokens"]
|
|
122
|
+
|
|
120
123
|
# Specify setting for metric, will be called within certain metrics
|
|
121
124
|
metric_setting:
|
|
122
125
|
retrieval_recall_topk: 5
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/retriever/encoder.py
RENAMED
|
@@ -24,7 +24,7 @@ class Encoder:
|
|
|
24
24
|
Encodes a list of queries into embeddings.
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
|
-
def __init__(self, model_name, model_path, pooling_method, max_length, use_fp16=True, instruction=
|
|
27
|
+
def __init__(self, model_name, model_path, pooling_method, max_length, use_fp16=True, instruction=None, silent=False):
|
|
28
28
|
self.model_name = model_name
|
|
29
29
|
self.model_path = model_path
|
|
30
30
|
self.pooling_method = pooling_method
|
|
@@ -1,45 +1,62 @@
|
|
|
1
|
-
import os
|
|
2
1
|
import re
|
|
2
|
+
import time
|
|
3
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
4
|
+
|
|
3
5
|
import faiss
|
|
4
6
|
import json
|
|
5
7
|
import warnings
|
|
6
8
|
import numpy as np
|
|
7
|
-
from typing import
|
|
9
|
+
from typing import Dict, List
|
|
8
10
|
import shutil
|
|
9
11
|
import subprocess
|
|
10
12
|
import argparse
|
|
11
13
|
import datasets
|
|
12
14
|
import torch
|
|
15
|
+
from seismic import SeismicIndex
|
|
13
16
|
from tqdm import tqdm
|
|
14
17
|
from flashrag.retriever.utils import load_model, load_corpus, pooling, set_default_instruction, judge_zh
|
|
18
|
+
from transformers import AutoTokenizer, AutoModelForMaskedLM
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import multiprocessing
|
|
22
|
+
|
|
23
|
+
cores = str(multiprocessing.cpu_count())
|
|
24
|
+
os.environ["RAYON_NUM_THREADS"] = cores
|
|
15
25
|
|
|
16
26
|
|
|
17
27
|
class Index_Builder:
|
|
18
28
|
r"""A tool class used to build an index used in retrieval."""
|
|
19
29
|
|
|
20
30
|
def __init__(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
self,
|
|
32
|
+
retrieval_method,
|
|
33
|
+
model_path,
|
|
34
|
+
corpus_path,
|
|
35
|
+
save_dir,
|
|
36
|
+
max_length,
|
|
37
|
+
batch_size,
|
|
38
|
+
use_fp16,
|
|
39
|
+
n_postings=1000,
|
|
40
|
+
centroid_fraction=0.2,
|
|
41
|
+
min_cluster_size=2,
|
|
42
|
+
summary_energy=0.4,
|
|
43
|
+
batched_indexing=10000,
|
|
44
|
+
corpus_embedded_path=None,
|
|
45
|
+
pooling_method=None,
|
|
46
|
+
instruction=None,
|
|
47
|
+
faiss_type=None,
|
|
48
|
+
embedding_path=None,
|
|
49
|
+
save_embedding=False,
|
|
50
|
+
faiss_gpu=False,
|
|
51
|
+
use_sentence_transformer=False,
|
|
52
|
+
bm25_backend="bm25s",
|
|
53
|
+
index_modal="all",
|
|
54
|
+
nknn=0,
|
|
38
55
|
):
|
|
39
|
-
|
|
40
56
|
self.retrieval_method = retrieval_method.lower()
|
|
41
57
|
self.model_path = model_path
|
|
42
58
|
self.corpus_path = corpus_path
|
|
59
|
+
self.corpus_embedded_path = corpus_embedded_path
|
|
43
60
|
self.save_dir = save_dir
|
|
44
61
|
self.max_length = max_length
|
|
45
62
|
self.batch_size = batch_size
|
|
@@ -52,6 +69,12 @@ class Index_Builder:
|
|
|
52
69
|
self.use_sentence_transformer = use_sentence_transformer
|
|
53
70
|
self.bm25_backend = bm25_backend
|
|
54
71
|
self.index_modal = index_modal
|
|
72
|
+
self.n_postings = n_postings
|
|
73
|
+
self.centroid_fraction = centroid_fraction
|
|
74
|
+
self.min_cluster_size = min_cluster_size
|
|
75
|
+
self.summary_energy = summary_energy
|
|
76
|
+
self.batched_indexing = batched_indexing
|
|
77
|
+
self.nknn = nknn
|
|
55
78
|
|
|
56
79
|
# judge if the retrieval model is clip
|
|
57
80
|
self.is_clip = ("clip" in self.retrieval_method) or (self.model_path is not None and "clip" in self.model_path)
|
|
@@ -127,9 +150,236 @@ class Index_Builder:
|
|
|
127
150
|
self.build_bm25_index_bm25s()
|
|
128
151
|
else:
|
|
129
152
|
assert False, "Invalid bm25 backend!"
|
|
153
|
+
elif self.retrieval_method == "splade":
|
|
154
|
+
self.build_seismic_index()
|
|
130
155
|
else:
|
|
131
156
|
self.build_dense_index()
|
|
132
157
|
|
|
158
|
+
def build_seismic_index(self):
|
|
159
|
+
"""Build Seismic index after saving documents in required JSONL format using batch processing."""
|
|
160
|
+
|
|
161
|
+
r"""Full Command:
|
|
162
|
+
# Use "splade" (sparse embedding neural model) as retrieval method to trigger sysmic index costruction.
|
|
163
|
+
python -m flashrag.retriever.index_builder \ # builder
|
|
164
|
+
--retrieval_method splade \ # Model name to trigger seismic index (splade only available)
|
|
165
|
+
--model_path retriever/splade-v3 \ # Local path or repository path are both supported.
|
|
166
|
+
--corpus_embedded_path data/ms_marco/ms_marco_embedded_corpus.jsonl \ # Use cached embedded corpus if corpus is already available ins seismic expected format
|
|
167
|
+
--corpus_path data/ms_marco/ms_marco_corpus.jsonl \ # Corpus path in format {id, contents} jsonl file to be embedded if not already built
|
|
168
|
+
--save_dir indexes/ \ # save index directory
|
|
169
|
+
--use_fp16 \ # tell to use fp16 for splade model
|
|
170
|
+
--max_length 512 \ # max tokens for each document
|
|
171
|
+
--batch_size 4 \ # batch size for splade model (Suggested between 2 and 24 for 16GB VRAM)
|
|
172
|
+
--n_postings 1000 \ # seismic number of posting lists
|
|
173
|
+
--centroid_fraction 0.2 \ # seismic centroids
|
|
174
|
+
--min_cluster_size 2 \ # seismic min cluster
|
|
175
|
+
--summary_energy 0.4 \ # seismic energy
|
|
176
|
+
--batched_indexing 10000 # seismic batch
|
|
177
|
+
--nknn 32
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
if self.pooling_method != 'max':
|
|
181
|
+
print(
|
|
182
|
+
f'Pooling method: {self.pooling_method.upper()} not supported on sparse neural retrieval models. fallback to: MAX.')
|
|
183
|
+
# Load document encoder model (only splade i currently implemented at the moment)
|
|
184
|
+
tokenizer = AutoTokenizer.from_pretrained(self.model_path)
|
|
185
|
+
model = AutoModelForMaskedLM.from_pretrained(self.model_path)
|
|
186
|
+
# Use half precision
|
|
187
|
+
if self.use_fp16:
|
|
188
|
+
model = model.half()
|
|
189
|
+
# Use more devices if available
|
|
190
|
+
if torch.cuda.device_count() > 1:
|
|
191
|
+
print(f"Using {torch.cuda.device_count()} GPUs")
|
|
192
|
+
model = torch.nn.DataParallel(model)
|
|
193
|
+
|
|
194
|
+
# Load to cuda
|
|
195
|
+
model = model.to('cuda' if torch.cuda.is_available() else 'cpu')
|
|
196
|
+
model.eval()
|
|
197
|
+
|
|
198
|
+
# Create file for embedded corpus
|
|
199
|
+
corpus_name = os.path.splitext(os.path.basename(self.corpus_path))[0]
|
|
200
|
+
output_path = os.path.join(self.save_dir, f"{corpus_name}_{self.retrieval_method}_embedded.jsonl")
|
|
201
|
+
|
|
202
|
+
# Init vars
|
|
203
|
+
total_docs = len(self.corpus)
|
|
204
|
+
processed_docs = 0
|
|
205
|
+
start_time = time.time()
|
|
206
|
+
last_update = start_time
|
|
207
|
+
|
|
208
|
+
if self.corpus_embedded_path:
|
|
209
|
+
print("Using cached corpus in seismic format.")
|
|
210
|
+
if self.nknn > 0:
|
|
211
|
+
index = SeismicIndex.build(
|
|
212
|
+
self.corpus_embedded_path,
|
|
213
|
+
n_postings=self.n_postings,
|
|
214
|
+
centroid_fraction=self.centroid_fraction,
|
|
215
|
+
min_cluster_size=self.min_cluster_size,
|
|
216
|
+
summary_energy=self.summary_energy,
|
|
217
|
+
batched_indexing=self.batched_indexing,
|
|
218
|
+
num_threads=int(cores),
|
|
219
|
+
nknn=self.nknn
|
|
220
|
+
)
|
|
221
|
+
else:
|
|
222
|
+
index = SeismicIndex.build(
|
|
223
|
+
self.corpus_embedded_path,
|
|
224
|
+
n_postings=self.n_postings,
|
|
225
|
+
centroid_fraction=self.centroid_fraction,
|
|
226
|
+
min_cluster_size=self.min_cluster_size,
|
|
227
|
+
summary_energy=self.summary_energy,
|
|
228
|
+
batched_indexing=self.batched_indexing,
|
|
229
|
+
num_threads=int(cores)
|
|
230
|
+
)
|
|
231
|
+
index.save(os.path.join(self.save_dir, f"{corpus_name}_{self.retrieval_method}"))
|
|
232
|
+
return index
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
with open(output_path, 'w') as f_out, ThreadPoolExecutor(max_workers=1) as pool:
|
|
236
|
+
# create progress bar
|
|
237
|
+
progress_bar = tqdm(
|
|
238
|
+
desc="Processing Documents",
|
|
239
|
+
total=total_docs,
|
|
240
|
+
unit="doc",
|
|
241
|
+
bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}]"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
batch = []
|
|
245
|
+
for doc in self.corpus:
|
|
246
|
+
# Load batch
|
|
247
|
+
batch.append(doc)
|
|
248
|
+
|
|
249
|
+
# On batch ready process batch
|
|
250
|
+
if len(batch) >= self.batch_size:
|
|
251
|
+
self._process_batch(batch, f_out, model, tokenizer, pool)
|
|
252
|
+
processed_docs += len(batch)
|
|
253
|
+
batch = []
|
|
254
|
+
|
|
255
|
+
# Progress updates
|
|
256
|
+
current_time = time.time()
|
|
257
|
+
if processed_docs % 100 == 0 or (current_time - last_update) > 5.0:
|
|
258
|
+
elapsed = current_time - start_time
|
|
259
|
+
docs_per_sec = processed_docs / elapsed
|
|
260
|
+
remaining = (total_docs - processed_docs) / docs_per_sec
|
|
261
|
+
|
|
262
|
+
progress_bar.set_postfix({
|
|
263
|
+
'speed': f"{docs_per_sec:.1f} docs/s",
|
|
264
|
+
'ETA': f"{remaining / 60:.1f} min"
|
|
265
|
+
})
|
|
266
|
+
progress_bar.update(100 if processed_docs % 100 == 0 else processed_docs % 100)
|
|
267
|
+
last_update = current_time
|
|
268
|
+
|
|
269
|
+
# Handle final leftover batch
|
|
270
|
+
if batch:
|
|
271
|
+
self._process_batch(batch, f_out, model, tokenizer, pool)
|
|
272
|
+
progress_bar.update(len(batch))
|
|
273
|
+
|
|
274
|
+
progress_bar.close()
|
|
275
|
+
|
|
276
|
+
if self.nknn > 0:
|
|
277
|
+
index = SeismicIndex.build(
|
|
278
|
+
output_path,
|
|
279
|
+
n_postings=self.n_postings,
|
|
280
|
+
centroid_fraction=self.centroid_fraction,
|
|
281
|
+
min_cluster_size=self.min_cluster_size,
|
|
282
|
+
summary_energy=self.summary_energy,
|
|
283
|
+
batched_indexing=self.batched_indexing,
|
|
284
|
+
num_threads=int(cores),
|
|
285
|
+
nknn=self.nknn
|
|
286
|
+
)
|
|
287
|
+
else:
|
|
288
|
+
# Create index on embedded corpus file and save it
|
|
289
|
+
index = SeismicIndex.build(
|
|
290
|
+
output_path,
|
|
291
|
+
n_postings=self.n_postings,
|
|
292
|
+
centroid_fraction=self.centroid_fraction,
|
|
293
|
+
min_cluster_size=self.min_cluster_size,
|
|
294
|
+
summary_energy=self.summary_energy,
|
|
295
|
+
batched_indexing=self.batched_indexing,
|
|
296
|
+
num_threads=int(cores)
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
index.save(os.path.join(self.save_dir, f"{corpus_name}_{self.retrieval_method}"))
|
|
300
|
+
return index
|
|
301
|
+
|
|
302
|
+
def _process_batch(self, batch, f_out, model, tokenizer, pool):
|
|
303
|
+
# Get embeddings
|
|
304
|
+
texts = [doc['contents'] for doc in batch]
|
|
305
|
+
vectors = self._get_sparse_embedding(texts, model, tokenizer)
|
|
306
|
+
|
|
307
|
+
# create json for the batch
|
|
308
|
+
def save(docs, embs):
|
|
309
|
+
for doc, vector in zip(docs, embs):
|
|
310
|
+
if 'vector' not in doc:
|
|
311
|
+
doc['vector'] = vector
|
|
312
|
+
f_out.write(json.dumps({
|
|
313
|
+
"id": str(doc['id']),
|
|
314
|
+
"contents": doc['contents'],
|
|
315
|
+
"vector": doc['vector']
|
|
316
|
+
}) + "\n")
|
|
317
|
+
pool.submit(save, batch, vectors)
|
|
318
|
+
|
|
319
|
+
def _get_sparse_embedding(self, texts: List[str], model, tokenizer) -> List[Dict[str, float]]:
|
|
320
|
+
"""Generate sparse embeddings for a batch of texts."""
|
|
321
|
+
inputs = tokenizer(
|
|
322
|
+
texts,
|
|
323
|
+
return_tensors="pt",
|
|
324
|
+
truncation=True,
|
|
325
|
+
padding=True,
|
|
326
|
+
max_length=self.max_length,
|
|
327
|
+
add_special_tokens=True
|
|
328
|
+
).to(model.device)
|
|
329
|
+
|
|
330
|
+
with torch.no_grad():
|
|
331
|
+
logits = model(**inputs).logits # [batch_size, seq_len, vocab_size]
|
|
332
|
+
attention_mask = inputs["attention_mask"].unsqueeze(-1)
|
|
333
|
+
|
|
334
|
+
scores = torch.log1p(torch.relu(logits)) * attention_mask
|
|
335
|
+
|
|
336
|
+
v_repr = torch.max(scores, dim=1)[0] # [batch_size, vocab_size]
|
|
337
|
+
|
|
338
|
+
# Move to CPU (it seems much faster)
|
|
339
|
+
v_repr = v_repr.cpu()
|
|
340
|
+
nonzero_mask = v_repr > 1e-4
|
|
341
|
+
|
|
342
|
+
# Get sparse values and indices in batch
|
|
343
|
+
batch_indices, token_indices = torch.nonzero(nonzero_mask, as_tuple=True)
|
|
344
|
+
token_scores = v_repr[batch_indices, token_indices]
|
|
345
|
+
|
|
346
|
+
# Convert once all token IDs to strings (batched)
|
|
347
|
+
unique_token_ids = torch.unique(token_indices)
|
|
348
|
+
token_id_to_token = {
|
|
349
|
+
idx.item(): tok for idx, tok in zip(
|
|
350
|
+
unique_token_ids, tokenizer.convert_ids_to_tokens(unique_token_ids.tolist())
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
# Build final embeddings
|
|
355
|
+
from collections import defaultdict
|
|
356
|
+
embeddings = defaultdict(dict)
|
|
357
|
+
for b_idx, t_idx, score in zip(batch_indices, token_indices, token_scores):
|
|
358
|
+
embeddings[b_idx.item()][token_id_to_token[t_idx.item()]] = round(score.item(), 4)
|
|
359
|
+
|
|
360
|
+
# Convert to list for each document
|
|
361
|
+
return [embeddings[i] for i in range(len(texts))]
|
|
362
|
+
|
|
363
|
+
@staticmethod
|
|
364
|
+
def get_tokens_and_weights(sparse_embedding, tokenizer):
|
|
365
|
+
token_weight_dict = {}
|
|
366
|
+
for i in range(len(sparse_embedding.indices)):
|
|
367
|
+
token = tokenizer.decode([sparse_embedding.indices[i]])
|
|
368
|
+
weight = sparse_embedding.values[i]
|
|
369
|
+
token_weight_dict[token] = round(weight, 4)
|
|
370
|
+
|
|
371
|
+
# Sort the dictionary by weights
|
|
372
|
+
token_weight_dict = dict(sorted(token_weight_dict.items(), key=lambda item: item[1], reverse=True))
|
|
373
|
+
return token_weight_dict
|
|
374
|
+
|
|
375
|
+
@staticmethod
|
|
376
|
+
def clean_text(self, text: str) -> str:
|
|
377
|
+
"""Preprocess the text by removing special characters that seems to cause some problems in seismic index build resulting in a parse error."""
|
|
378
|
+
text = re.sub(r'<[^>]+>', '', text) # Remove HTML tags
|
|
379
|
+
text = re.sub(r'\s+', ' ', text) # Normalize multiple spaces
|
|
380
|
+
text = text.strip() # Remove leading/trailing whitespaces
|
|
381
|
+
return text
|
|
382
|
+
|
|
133
383
|
def build_bm25_index_pyserini(self):
|
|
134
384
|
"""Building BM25 index based on Pyserini library.
|
|
135
385
|
|
|
@@ -147,7 +397,7 @@ class Index_Builder:
|
|
|
147
397
|
shutil.copyfile(self.corpus_path, temp_file_path)
|
|
148
398
|
# check if the language is chinese
|
|
149
399
|
with open(self.corpus_path, 'r', encoding='utf-8') as file:
|
|
150
|
-
first_item = json.loads(file.readline())
|
|
400
|
+
first_item = json.loads(file.readline())
|
|
151
401
|
contents = first_item.get("contents", "") # 获取 contents 字段
|
|
152
402
|
zh_flag = judge_zh(contents)
|
|
153
403
|
elif self.corpus_path.endswith(".parquet"):
|
|
@@ -204,7 +454,7 @@ class Index_Builder:
|
|
|
204
454
|
else:
|
|
205
455
|
stemmer = Stemmer.Stemmer("english")
|
|
206
456
|
tokenizer = bm25s.tokenization.Tokenizer(stopwords='en', stemmer=stemmer)
|
|
207
|
-
|
|
457
|
+
|
|
208
458
|
corpus_text = corpus["contents"]
|
|
209
459
|
corpus_tokens = tokenizer.tokenize(corpus_text, return_as='tuple')
|
|
210
460
|
retriever = bm25s.BM25(corpus=corpus, backend="numba")
|
|
@@ -313,7 +563,7 @@ class Index_Builder:
|
|
|
313
563
|
if self.index_modal == "all":
|
|
314
564
|
assert all_embeddings.shape[0] % 2 == 0
|
|
315
565
|
text_embedding = all_embeddings[: len(all_embeddings) // 2, :]
|
|
316
|
-
image_embedding = all_embeddings[len(all_embeddings) // 2
|
|
566
|
+
image_embedding = all_embeddings[len(all_embeddings) // 2:, :]
|
|
317
567
|
text_index_save_path = os.path.join(
|
|
318
568
|
self.save_dir, f"{self.retrieval_method}_{self.faiss_type}_text.index"
|
|
319
569
|
)
|
|
@@ -336,10 +586,10 @@ class Index_Builder:
|
|
|
336
586
|
print("Finish!")
|
|
337
587
|
|
|
338
588
|
def save_faiss_index(
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
589
|
+
self,
|
|
590
|
+
all_embeddings,
|
|
591
|
+
faiss_type,
|
|
592
|
+
index_save_path,
|
|
343
593
|
):
|
|
344
594
|
# build index
|
|
345
595
|
print("Creating index")
|
|
@@ -363,6 +613,9 @@ class Index_Builder:
|
|
|
363
613
|
faiss.write_index(faiss_index, index_save_path)
|
|
364
614
|
|
|
365
615
|
|
|
616
|
+
import argparse
|
|
617
|
+
|
|
618
|
+
|
|
366
619
|
def main():
|
|
367
620
|
parser = argparse.ArgumentParser(description="Creating index.")
|
|
368
621
|
|
|
@@ -370,6 +623,7 @@ def main():
|
|
|
370
623
|
parser.add_argument("--retrieval_method", type=str)
|
|
371
624
|
parser.add_argument("--model_path", type=str, default=None)
|
|
372
625
|
parser.add_argument("--corpus_path", type=str)
|
|
626
|
+
parser.add_argument("--corpus_embedded_path", type=str, default=None)
|
|
373
627
|
parser.add_argument("--save_dir", default="indexes/", type=str)
|
|
374
628
|
|
|
375
629
|
# Parameters for building dense index
|
|
@@ -388,6 +642,14 @@ def main():
|
|
|
388
642
|
# Parameters for build multi-modal retriever index
|
|
389
643
|
parser.add_argument("--index_modal", type=str, default="all", choices=["text", "image", "all"])
|
|
390
644
|
|
|
645
|
+
# New arguments for seismic index
|
|
646
|
+
parser.add_argument("--n_postings", type=int, default=1000)
|
|
647
|
+
parser.add_argument("--centroid_fraction", type=float, default=0.2)
|
|
648
|
+
parser.add_argument("--min_cluster_size", type=int, default=2)
|
|
649
|
+
parser.add_argument("--summary_energy", type=float, default=0.4)
|
|
650
|
+
parser.add_argument("--nknn", type=int, default=0)
|
|
651
|
+
parser.add_argument("--batched_indexing", type=int, default=10000)
|
|
652
|
+
|
|
391
653
|
args = parser.parse_args()
|
|
392
654
|
|
|
393
655
|
index_builder = Index_Builder(
|
|
@@ -407,6 +669,13 @@ def main():
|
|
|
407
669
|
use_sentence_transformer=args.sentence_transformer,
|
|
408
670
|
bm25_backend=args.bm25_backend,
|
|
409
671
|
index_modal=args.index_modal,
|
|
672
|
+
n_postings=args.n_postings,
|
|
673
|
+
centroid_fraction=args.centroid_fraction,
|
|
674
|
+
min_cluster_size=args.min_cluster_size,
|
|
675
|
+
summary_energy=args.summary_energy,
|
|
676
|
+
batched_indexing=args.batched_indexing,
|
|
677
|
+
corpus_embedded_path=args.corpus_embedded_path,
|
|
678
|
+
nknn=args.nknn
|
|
410
679
|
)
|
|
411
680
|
index_builder.build_index()
|
|
412
681
|
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/retriever/retriever.py
RENAMED
|
@@ -15,6 +15,7 @@ from flashrag.utils import get_reranker
|
|
|
15
15
|
from flashrag.retriever.utils import load_corpus, load_docs, convert_numpy, judge_image, judge_zh
|
|
16
16
|
from flashrag.retriever.encoder import Encoder, STEncoder, ClipEncoder
|
|
17
17
|
|
|
18
|
+
import torch
|
|
18
19
|
|
|
19
20
|
def cache_manager(func):
|
|
20
21
|
"""
|
|
@@ -932,3 +933,209 @@ class MultiRetrieverRouter:
|
|
|
932
933
|
final_result.append(image_result[image_idx])
|
|
933
934
|
image_idx += 1
|
|
934
935
|
return final_result
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
class SparseRetriever(BaseTextRetriever):
|
|
939
|
+
"""Sparse embedding retriever supporting only SPLADE with Seismic backend for now."""
|
|
940
|
+
|
|
941
|
+
def __init__(self, config):
|
|
942
|
+
super().__init__(config)
|
|
943
|
+
|
|
944
|
+
import multiprocessing
|
|
945
|
+
self.cores = str(multiprocessing.cpu_count())
|
|
946
|
+
os.environ["RAYON_NUM_THREADS"] = self.cores
|
|
947
|
+
|
|
948
|
+
self.progress_bar = None
|
|
949
|
+
|
|
950
|
+
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
|
951
|
+
self.corpus = load_corpus(config["corpus_path"])
|
|
952
|
+
self.tokenizer, self.model = self._load_sparse_model()
|
|
953
|
+
|
|
954
|
+
self.id = 0
|
|
955
|
+
|
|
956
|
+
self.update_additional_setting()
|
|
957
|
+
|
|
958
|
+
self.seismic_query_cut = self.config["seismic_query_cut"]
|
|
959
|
+
self.seismic_heap_factor = self.config["seismic_heap_factor"]
|
|
960
|
+
self.index_max_tokens = self.config["seismic_max_tokens_length"]
|
|
961
|
+
self._init_seismic_index()
|
|
962
|
+
|
|
963
|
+
def update_additional_setting(self):
|
|
964
|
+
"""Load config shared for all the models supported"""
|
|
965
|
+
self.query_max_length = self._config["retrieval_query_max_length"]
|
|
966
|
+
self.use_fp16 = self._config["retrieval_use_fp16"]
|
|
967
|
+
self.batch_size = self._config["retrieval_batch_size"]
|
|
968
|
+
self.retrieval_model_path = self._config["retrieval_model_path"]
|
|
969
|
+
self.pooling_method = self._config["retrieval_pooling_method"]
|
|
970
|
+
|
|
971
|
+
def _init_seismic_index(self):
|
|
972
|
+
"""Initialize Seismic index."""
|
|
973
|
+
from seismic import SeismicIndex # Assuming this is available
|
|
974
|
+
self.seismic_index = SeismicIndex.load(self.index_path)
|
|
975
|
+
self.string_type = f'U{self.index_max_tokens}' # For Seismic string dtype
|
|
976
|
+
|
|
977
|
+
def _load_sparse_model(self):
|
|
978
|
+
"""Load tokenizer and model based on sparse type."""
|
|
979
|
+
from transformers import AutoModelForMaskedLM, AutoTokenizer
|
|
980
|
+
# Load model
|
|
981
|
+
tokenizer = AutoTokenizer.from_pretrained(self.retrieval_model_path)
|
|
982
|
+
model = AutoModelForMaskedLM.from_pretrained(self.retrieval_model_path)
|
|
983
|
+
|
|
984
|
+
if self.use_fp16:
|
|
985
|
+
model = model.half()
|
|
986
|
+
|
|
987
|
+
# Use more gpus if available
|
|
988
|
+
if torch.cuda.device_count() > 1:
|
|
989
|
+
model = torch.nn.DataParallel(model, device_ids=self.config['gpu_id'].split(','))
|
|
990
|
+
|
|
991
|
+
model = model.to(self.device)
|
|
992
|
+
model.eval()
|
|
993
|
+
return tokenizer, model
|
|
994
|
+
|
|
995
|
+
def _encode(self, query):
|
|
996
|
+
inputs = self.tokenizer(
|
|
997
|
+
query,
|
|
998
|
+
return_tensors="pt",
|
|
999
|
+
truncation=True,
|
|
1000
|
+
padding=True,
|
|
1001
|
+
max_length=self.query_max_length,
|
|
1002
|
+
add_special_tokens=True
|
|
1003
|
+
).to(self.model.device)
|
|
1004
|
+
|
|
1005
|
+
with torch.no_grad():
|
|
1006
|
+
logits = self.model(**inputs).logits # [batch_size, seq_len, vocab_size]
|
|
1007
|
+
attention_mask = inputs["attention_mask"].unsqueeze(-1) # [batch, seq_len, 1]
|
|
1008
|
+
|
|
1009
|
+
scores = torch.log1p(torch.relu(logits)) * attention_mask
|
|
1010
|
+
v_repr = torch.max(scores, dim=1)[0] # [batch_size, vocab_size]
|
|
1011
|
+
|
|
1012
|
+
# Move to CPU (it seems much faster)
|
|
1013
|
+
v_repr = v_repr.cpu()
|
|
1014
|
+
nonzero_mask = v_repr > 1e-4
|
|
1015
|
+
|
|
1016
|
+
# Get sparse values and indices in batch
|
|
1017
|
+
batch_indices, token_indices = torch.nonzero(nonzero_mask, as_tuple=True)
|
|
1018
|
+
token_scores = v_repr[batch_indices, token_indices]
|
|
1019
|
+
|
|
1020
|
+
# Convert once all token IDs to strings (batched)
|
|
1021
|
+
unique_token_ids = torch.unique(token_indices)
|
|
1022
|
+
token_id_to_token = {
|
|
1023
|
+
idx.item(): tok for idx, tok in zip(
|
|
1024
|
+
unique_token_ids, self.tokenizer.convert_ids_to_tokens(unique_token_ids.tolist())
|
|
1025
|
+
)
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
# Build final embeddings
|
|
1029
|
+
from collections import defaultdict
|
|
1030
|
+
embeddings = defaultdict(dict)
|
|
1031
|
+
for b_idx, t_idx, score in zip(batch_indices, token_indices, token_scores):
|
|
1032
|
+
embeddings[b_idx.item()][token_id_to_token[t_idx.item()]] = round(score.item(), 4)
|
|
1033
|
+
|
|
1034
|
+
# Convert to list for each document
|
|
1035
|
+
return [embeddings[i] for i in range(len(query))]
|
|
1036
|
+
|
|
1037
|
+
def search(self, query: list, num: int = None, return_score=False) -> (List[Dict], List[float]):
|
|
1038
|
+
"""Search using sparse vector."""
|
|
1039
|
+
num = num or self.topk
|
|
1040
|
+
|
|
1041
|
+
query_vec = self._encode(query)
|
|
1042
|
+
results, scores = self._seismic_search(query_vec, num)
|
|
1043
|
+
|
|
1044
|
+
if return_score:
|
|
1045
|
+
return results, scores
|
|
1046
|
+
else:
|
|
1047
|
+
return results
|
|
1048
|
+
|
|
1049
|
+
def batch_search(self, query, num=None, return_score=False):
|
|
1050
|
+
"""Search using sparse vector."""
|
|
1051
|
+
if isinstance(query, str):
|
|
1052
|
+
query = [query]
|
|
1053
|
+
|
|
1054
|
+
if self.pooling_method != 'max':
|
|
1055
|
+
print(
|
|
1056
|
+
f'Pooling method: {self.pooling_method.upper()} not supported on sparse neural retrieval models. fallback to: MAX.')
|
|
1057
|
+
|
|
1058
|
+
num = num or self.topk
|
|
1059
|
+
|
|
1060
|
+
embeddings = []
|
|
1061
|
+
batch = []
|
|
1062
|
+
|
|
1063
|
+
# Encode
|
|
1064
|
+
for i in range(len(query)):
|
|
1065
|
+
# Process batch
|
|
1066
|
+
batch.append(query[i])
|
|
1067
|
+
if len(batch) >= self.batch_size:
|
|
1068
|
+
query_vec = self._encode(batch)
|
|
1069
|
+
embeddings.extend(query_vec)
|
|
1070
|
+
batch = []
|
|
1071
|
+
|
|
1072
|
+
if batch:
|
|
1073
|
+
query_vec = self._encode(batch)
|
|
1074
|
+
embeddings.extend(query_vec)
|
|
1075
|
+
|
|
1076
|
+
# Search
|
|
1077
|
+
search_results = self._seismic_batch_search(embeddings, num)
|
|
1078
|
+
|
|
1079
|
+
results = []
|
|
1080
|
+
scores = []
|
|
1081
|
+
for result in sorted(search_results, key=lambda e: int(e[0][0])):
|
|
1082
|
+
tmp_results = []
|
|
1083
|
+
tmp_scores = []
|
|
1084
|
+
|
|
1085
|
+
for query_id, score, doc_id in result:
|
|
1086
|
+
tmp_results.append(self.corpus[int(doc_id)])
|
|
1087
|
+
tmp_scores.append(score)
|
|
1088
|
+
|
|
1089
|
+
results.append(tmp_results)
|
|
1090
|
+
scores.append(tmp_scores)
|
|
1091
|
+
|
|
1092
|
+
if return_score:
|
|
1093
|
+
return results, scores
|
|
1094
|
+
else:
|
|
1095
|
+
return results
|
|
1096
|
+
|
|
1097
|
+
def _seismic_search(self, query_vec: List[Dict[str, float]], k: int) -> (List[Dict], List[float]):
|
|
1098
|
+
"""Search using Seismic backend."""
|
|
1099
|
+
# Convert query to Seismic format
|
|
1100
|
+
results, scores = self.index_search(k, query_vec)
|
|
1101
|
+
return results[0], scores[0]
|
|
1102
|
+
|
|
1103
|
+
def _seismic_batch_search(self, query_vecs: List[Dict[str, float]], k: int) -> (
|
|
1104
|
+
List[List[Dict]], List[List[float]]):
|
|
1105
|
+
"""Batch search using Seismic backend (one query at a time)."""
|
|
1106
|
+
return self.index_search(k, query_vecs)
|
|
1107
|
+
|
|
1108
|
+
def index_search(self, k, query_vec):
|
|
1109
|
+
max_len = max(len(query) for query in query_vec)
|
|
1110
|
+
pad_token = "" # or whatever default is appropriate
|
|
1111
|
+
|
|
1112
|
+
query_components = []
|
|
1113
|
+
query_values = []
|
|
1114
|
+
ids = []
|
|
1115
|
+
|
|
1116
|
+
for query in query_vec:
|
|
1117
|
+
keys = list(query.keys())
|
|
1118
|
+
values = list(query.values())
|
|
1119
|
+
|
|
1120
|
+
# Pad to max_len
|
|
1121
|
+
padded_keys = keys + [pad_token] * (max_len - len(keys))
|
|
1122
|
+
padded_values = values + [0.0] * (max_len - len(values))
|
|
1123
|
+
|
|
1124
|
+
query_components.append(np.array(padded_keys, dtype='U30'))
|
|
1125
|
+
query_values.append(np.array(padded_values, dtype=np.float32))
|
|
1126
|
+
ids.append(self.id)
|
|
1127
|
+
self.id += 1
|
|
1128
|
+
|
|
1129
|
+
ids = np.array(ids, dtype='U30')
|
|
1130
|
+
# Execute search
|
|
1131
|
+
search_results = self.seismic_index.batch_search(
|
|
1132
|
+
queries_ids=ids, # Placeholder ID
|
|
1133
|
+
query_components=query_components,
|
|
1134
|
+
query_values=query_values,
|
|
1135
|
+
query_cut=self.seismic_query_cut,
|
|
1136
|
+
heap_factor=self.seismic_heap_factor,
|
|
1137
|
+
k=k,
|
|
1138
|
+
sorted=True, # specified even if default value
|
|
1139
|
+
num_threads=int(self.cores)
|
|
1140
|
+
)
|
|
1141
|
+
return search_results
|
|
@@ -28,7 +28,7 @@ def get_dataset(config):
|
|
|
28
28
|
continue
|
|
29
29
|
else:
|
|
30
30
|
print(f"Loading {split} dataset from: {split_path}...")
|
|
31
|
-
if split in ["test", "val", "dev"]:
|
|
31
|
+
if split in ["test", "val", "dev", "train"]:
|
|
32
32
|
split_dict[split] = Dataset(
|
|
33
33
|
config, split_path, sample_num=config["test_sample_num"], random_sample=config["random_sample"]
|
|
34
34
|
)
|
|
@@ -84,6 +84,8 @@ def get_retriever(config):
|
|
|
84
84
|
|
|
85
85
|
if config["retrieval_method"] == "bm25":
|
|
86
86
|
return getattr(importlib.import_module("flashrag.retriever"), "BM25Retriever")(config)
|
|
87
|
+
elif config["retrieval_method"] == "splade":
|
|
88
|
+
return getattr(importlib.import_module("flashrag.retriever"), "SparseRetriever")(config)
|
|
87
89
|
else:
|
|
88
90
|
try:
|
|
89
91
|
model_config = AutoConfig.from_pretrained(config["retrieval_model_path"])
|
|
@@ -180,4 +182,4 @@ def extract_between(text: str, start_tag: str, end_tag: str):
|
|
|
180
182
|
matches = re.findall(pattern, text, flags=re.DOTALL)
|
|
181
183
|
if matches:
|
|
182
184
|
return matches[-1].strip()
|
|
183
|
-
return None
|
|
185
|
+
return None
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.4dev20250720"
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: flashrag-dev
|
|
3
|
+
Version: 0.1.4.dev20250720
|
|
4
|
+
Summary: A library for efficient Retrieval-Augmented Generation research
|
|
5
|
+
Home-page: https://github.com/RUC-NLPIR/FlashRAG
|
|
6
|
+
Author: Jiajie Jin, Yutao Zhu, Chenghao Zhang, Xinyu Yang, Zhicheng Dou
|
|
7
|
+
Author-email: jinjiajie@ruc.edu.cn
|
|
8
|
+
License: MIT License
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Provides-Extra: core
|
|
12
|
+
Provides-Extra: retriever
|
|
13
|
+
Provides-Extra: generator
|
|
14
|
+
Provides-Extra: multimodal
|
|
15
|
+
Provides-Extra: full
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
|
|
1
18
|
# <div align="center">⚡FlashRAG: A Python Toolkit for Efficient RAG Research<div>
|
|
2
19
|
\[ English | [中文](README_zh.md) \]
|
|
3
20
|
<div align="center">
|
|
@@ -261,6 +278,32 @@ python -m flashrag.retriever.index_builder \
|
|
|
261
278
|
--save_dir indexes/
|
|
262
279
|
```
|
|
263
280
|
|
|
281
|
+
### For Sparse Neural Retrieval Methods (SPLADE)
|
|
282
|
+
|
|
283
|
+
##### Install Seismic Index:
|
|
284
|
+
```bash
|
|
285
|
+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Install Rust for compiling
|
|
286
|
+
pip install pyseismic-lsr # Install Seismic
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
##### Then build the index with Seismic:
|
|
290
|
+
```bash
|
|
291
|
+
python -m flashrag.retriever.index_builder \ # builder
|
|
292
|
+
--retrieval_method splade \ # Model name to trigger seismic index (splade only available)
|
|
293
|
+
--model_path retriever/splade-v3 \ # Local path or repository path are both supported.
|
|
294
|
+
--corpus_embedded_path data/ms_marco/ms_marco_embedded_corpus.jsonl \ # Use cached embedded corpus if corpus is already available in seismic expected format
|
|
295
|
+
--corpus_path data/ms_marco/ms_marco_corpus.jsonl \ # Corpus path in format {id, contents} jsonl file to be embedded if not already built
|
|
296
|
+
--save_dir indexes/ \ # save index directory
|
|
297
|
+
--use_fp16 \ # tell to use fp16 for splade model
|
|
298
|
+
--max_length 512 \ # max tokens for each document
|
|
299
|
+
--batch_size 4 \ # batch size for splade model (4-5 seems the best size for Tesla T4 16GB)
|
|
300
|
+
--n_postings 1000 \ # seismic number of posting lists
|
|
301
|
+
--centroid_fraction 0.2 \ # seismic centroids
|
|
302
|
+
--min_cluster_size 2 \ # seismic min cluster
|
|
303
|
+
--summary_energy 0.4 \ # seismic energy
|
|
304
|
+
--batched_indexing 10000000 # seismic batch
|
|
305
|
+
--nknn 32 # Optional parameter. Tell to seismic to use also knn graph. if not present seismic will work without knn graph
|
|
306
|
+
```
|
|
264
307
|
### Using the ready-made pipeline
|
|
265
308
|
|
|
266
309
|
You can use the pipeline class we have already built (as shown in [<u>pipelines</u>](#pipelines)) to implement the RAG process inside. In this case, you just need to configure the config and load the corresponding pipeline.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.4dev20250623"
|
|
File without changes
|
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/config/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/dataset/__init__.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/dataset/dataset.py
RENAMED
|
File without changes
|
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/evaluator/__init__.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/evaluator/_bleu.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/evaluator/evaluator.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/evaluator/metrics.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/evaluator/utils.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/generator/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/generator/generator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/generator/utils.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/judger/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/pipeline/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/pipeline/mm_pipeline.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/pipeline/pipeline.py
RENAMED
|
File without changes
|
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/pipeline/replug_utils.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/prompt/__init__.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/prompt/base_prompt.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/prompt/mm_prompt.py
RENAMED
|
File without changes
|
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/prompt/trace_examplars.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/refiner/__init__.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/refiner/kg_refiner.py
RENAMED
|
File without changes
|
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/refiner/refiner.py
RENAMED
|
File without changes
|
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/retriever/__init__.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/retriever/__main__.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/retriever/reranker.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/retriever/utils.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/utils/__init__.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/utils/constants.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag/utils/pred_parse.py
RENAMED
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag_dev.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{flashrag_dev-0.1.4.dev20250623 → flashrag_dev-0.1.4.dev20250720}/flashrag_dev.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|