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.
- flashrag/__init__.py +0 -0
- flashrag/config/__init__.py +2 -0
- flashrag/config/config.py +219 -0
- flashrag/dataset/__init__.py +2 -0
- flashrag/dataset/dataset.py +160 -0
- flashrag/dataset/utils.py +60 -0
- flashrag/evaluator/__init__.py +2 -0
- flashrag/evaluator/_bleu.py +209 -0
- flashrag/evaluator/evaluator.py +82 -0
- flashrag/evaluator/metrics.py +508 -0
- flashrag/evaluator/utils.py +19 -0
- flashrag/generator/__init__.py +2 -0
- flashrag/generator/fid.py +247 -0
- flashrag/generator/generator.py +623 -0
- flashrag/generator/openai_generator.py +96 -0
- flashrag/generator/stop_word_criteria.py +105 -0
- flashrag/judger/__init__.py +1 -0
- flashrag/judger/judger.py +184 -0
- flashrag/pipeline/__init__.py +3 -0
- flashrag/pipeline/active_pipeline.py +980 -0
- flashrag/pipeline/branching_pipeline.py +250 -0
- flashrag/pipeline/pipeline.py +248 -0
- flashrag/pipeline/replug_utils.py +249 -0
- flashrag/prompt/__init__.py +1 -0
- flashrag/prompt/base_prompt.py +165 -0
- flashrag/prompt/selfask_examplars.py +108 -0
- flashrag/prompt/trace_examplars.py +4015 -0
- flashrag/refiner/__init__.py +2 -0
- flashrag/refiner/kg_refiner.py +612 -0
- flashrag/refiner/llmlingua_compressor.py +2360 -0
- flashrag/refiner/refiner.py +252 -0
- flashrag/refiner/selective_context_compressor.py +294 -0
- flashrag/retriever/__init__.py +3 -0
- flashrag/retriever/__main__.py +4 -0
- flashrag/retriever/encoder.py +120 -0
- flashrag/retriever/index_builder.py +334 -0
- flashrag/retriever/reranker.py +145 -0
- flashrag/retriever/retriever.py +346 -0
- flashrag/retriever/utils.py +49 -0
- flashrag/utils/__init__.py +2 -0
- flashrag/utils/constants.py +51 -0
- flashrag/utils/pred_parse.py +25 -0
- flashrag/utils/utils.py +135 -0
- flashrag_dev-0.1.1.dist-info/LICENSE +21 -0
- flashrag_dev-0.1.1.dist-info/METADATA +616 -0
- flashrag_dev-0.1.1.dist-info/RECORD +48 -0
- flashrag_dev-0.1.1.dist-info/WHEEL +5 -0
- flashrag_dev-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,2360 @@
|
|
|
1
|
+
# Implementation of LLMLingua, modified from official repo: https://github.com/microsoft/LLMLingua.
|
|
2
|
+
# Copyright (c) 2023 Microsoft
|
|
3
|
+
# Licensed under The MIT License
|
|
4
|
+
|
|
5
|
+
import bisect
|
|
6
|
+
import copy
|
|
7
|
+
import json
|
|
8
|
+
import re
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
from typing import List, Union
|
|
11
|
+
import string
|
|
12
|
+
import yaml
|
|
13
|
+
import nltk
|
|
14
|
+
import numpy as np
|
|
15
|
+
import tiktoken
|
|
16
|
+
import torch
|
|
17
|
+
import torch.nn.functional as F
|
|
18
|
+
from torch.utils.data import DataLoader, Dataset
|
|
19
|
+
from transformers import (
|
|
20
|
+
AutoConfig,
|
|
21
|
+
AutoModelForCausalLM,
|
|
22
|
+
AutoModelForTokenClassification,
|
|
23
|
+
AutoTokenizer,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TokenClfDataset(Dataset):
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
texts,
|
|
31
|
+
max_len=512,
|
|
32
|
+
tokenizer=None,
|
|
33
|
+
model_name="bert-base-multilingual-cased",
|
|
34
|
+
):
|
|
35
|
+
self.len = len(texts)
|
|
36
|
+
self.texts = texts
|
|
37
|
+
self.tokenizer = tokenizer
|
|
38
|
+
self.max_len = max_len
|
|
39
|
+
self.model_name = model_name
|
|
40
|
+
if "bert-base-multilingual-cased" in model_name:
|
|
41
|
+
self.cls_token = "[CLS]"
|
|
42
|
+
self.sep_token = "[SEP]"
|
|
43
|
+
self.unk_token = "[UNK]"
|
|
44
|
+
self.pad_token = "[PAD]"
|
|
45
|
+
self.mask_token = "[MASK]"
|
|
46
|
+
elif "xlm-roberta-large" in model_name:
|
|
47
|
+
self.bos_token = "<s>"
|
|
48
|
+
self.eos_token = "</s>"
|
|
49
|
+
self.sep_token = "</s>"
|
|
50
|
+
self.cls_token = "<s>"
|
|
51
|
+
self.unk_token = "<unk>"
|
|
52
|
+
self.pad_token = "<pad>"
|
|
53
|
+
self.mask_token = "<mask>"
|
|
54
|
+
else:
|
|
55
|
+
raise NotImplementedError()
|
|
56
|
+
|
|
57
|
+
def __getitem__(self, index):
|
|
58
|
+
text = self.texts[index]
|
|
59
|
+
tokenized_text = self.tokenizer.tokenize(text)
|
|
60
|
+
|
|
61
|
+
tokenized_text = [self.cls_token] + tokenized_text + [self.sep_token] # add special tokens
|
|
62
|
+
|
|
63
|
+
if len(tokenized_text) > self.max_len:
|
|
64
|
+
tokenized_text = tokenized_text[: self.max_len]
|
|
65
|
+
else:
|
|
66
|
+
tokenized_text = tokenized_text + [self.pad_token for _ in range(self.max_len - len(tokenized_text))]
|
|
67
|
+
|
|
68
|
+
attn_mask = [1 if tok != self.pad_token else 0 for tok in tokenized_text]
|
|
69
|
+
|
|
70
|
+
ids = self.tokenizer.convert_tokens_to_ids(tokenized_text)
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
"ids": torch.tensor(ids, dtype=torch.long),
|
|
74
|
+
"mask": torch.tensor(attn_mask, dtype=torch.long),
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
def __len__(self):
|
|
78
|
+
return self.len
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def is_begin_of_new_word(token, model_name, force_tokens, token_map):
|
|
82
|
+
if "bert-base-multilingual-cased" in model_name:
|
|
83
|
+
if token.lstrip("##") in force_tokens or token.lstrip("##") in set(token_map.values()):
|
|
84
|
+
return True
|
|
85
|
+
return not token.startswith("##")
|
|
86
|
+
elif "xlm-roberta-large" in model_name:
|
|
87
|
+
if token in string.punctuation or token in force_tokens or token in set(token_map.values()):
|
|
88
|
+
return True
|
|
89
|
+
return token.startswith("▁")
|
|
90
|
+
else:
|
|
91
|
+
raise NotImplementedError()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def replace_added_token(token, token_map):
|
|
95
|
+
for ori_token, new_token in token_map.items():
|
|
96
|
+
token = token.replace(new_token, ori_token)
|
|
97
|
+
return token
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def get_pure_token(token, model_name):
|
|
101
|
+
if "bert-base-multilingual-cased" in model_name:
|
|
102
|
+
return token.lstrip("##")
|
|
103
|
+
elif "xlm-roberta-large" in model_name:
|
|
104
|
+
return token.lstrip("▁")
|
|
105
|
+
else:
|
|
106
|
+
raise NotImplementedError()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def process_structured_json_data(json_data, json_config):
|
|
110
|
+
if isinstance(json_config, str):
|
|
111
|
+
with open(json_config, "r") as file:
|
|
112
|
+
json_config = yaml.safe_load(file)
|
|
113
|
+
elif not isinstance(json_config, dict):
|
|
114
|
+
raise ValueError("Invalid json config file. It should be a dictionary or a path to a yaml file.")
|
|
115
|
+
assert set(json_data.keys()) == set(json_config.keys()), "Keys in json data and json config file do not match."
|
|
116
|
+
context = ["<llmlingua, compress=False>{</llmlingua>"]
|
|
117
|
+
forced_context_ids = [0]
|
|
118
|
+
for i, (k, v) in enumerate(json_data.items()):
|
|
119
|
+
if not json_config[k]["pair_remove"]:
|
|
120
|
+
forced_context_ids.append(i + 1)
|
|
121
|
+
rate, compress, value_type = (
|
|
122
|
+
json_config[k]["rate"],
|
|
123
|
+
json_config[k]["compress"],
|
|
124
|
+
json_config[k]["value_type"],
|
|
125
|
+
)
|
|
126
|
+
if not compress:
|
|
127
|
+
rate = 1
|
|
128
|
+
context.append(precess_jsonKVpair(k, v, value_type, rate))
|
|
129
|
+
context[-1] = context[-1][:-14] + "</llmlingua>"
|
|
130
|
+
context.append("<llmlingua, compress=False>}</llmlingua>")
|
|
131
|
+
forced_context_ids.append(len(json_data) + 1)
|
|
132
|
+
|
|
133
|
+
return context, forced_context_ids
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def precess_jsonKVpair(k, v, value_type, rate):
|
|
137
|
+
if rate == 1:
|
|
138
|
+
return "<llmlingua, compress=False>" + f"{json.dumps({k:v})[1:-1]}, " + "</llmlingua>"
|
|
139
|
+
if value_type == "str" or value_type == "string":
|
|
140
|
+
v = str(v)
|
|
141
|
+
new_v = f"</llmlingua><llmlingua, rate={rate}>" + v + "</llmlingua><llmlingua, compress=False>"
|
|
142
|
+
return "<llmlingua, compress=False>" + f"{json.dumps({k:new_v})[1:-1]}, " + "</llmlingua>"
|
|
143
|
+
elif value_type in ["int", "float", "integer", "number"]:
|
|
144
|
+
if value_type in ["int", "integer"]:
|
|
145
|
+
v = int(v)
|
|
146
|
+
if value_type in ["float", "number"]:
|
|
147
|
+
v = float(v)
|
|
148
|
+
return (
|
|
149
|
+
"<llmlingua, compress=False>"
|
|
150
|
+
+ f'"{k}": </llmlingua><llmlingua, rate={rate}>{v}</llmlingua><llmlingua, compress=False>, </llmlingua>'
|
|
151
|
+
)
|
|
152
|
+
elif value_type == "bool" or value_type == "boolean":
|
|
153
|
+
if v in ["True", "true", "TRUE", True]:
|
|
154
|
+
v = "true"
|
|
155
|
+
elif v in ["False", "false", "FALSE", False]:
|
|
156
|
+
v = "false"
|
|
157
|
+
else:
|
|
158
|
+
raise ValueError(f"Invalid boolean value: {v}")
|
|
159
|
+
new_v = f"</llmlingua><llmlingua, rate={rate}>" + v + "</llmlingua><llmlingua, compress=False>"
|
|
160
|
+
return "<llmlingua, compress=False>" + f"{json.dumps({k:new_v})[1:-1]}, " + "</llmlingua>"
|
|
161
|
+
elif value_type == "list" or value_type == "List":
|
|
162
|
+
return "<llmlingua, compress=False>" + f'"{k}": {process_sequence_data(rate, "[", "]", v)}'
|
|
163
|
+
elif value_type == "dict" or value_type == "dictionary":
|
|
164
|
+
return "<llmlingua, compress=False>" + f'"{k}": {process_sequence_data(rate, "[", "]", v, is_dict=True)}'
|
|
165
|
+
elif value_type == "set":
|
|
166
|
+
raise ValueError(f"Invalid value type: {value_type}")
|
|
167
|
+
# return '<llmlingua, compress=False>' + f'"{k}": {process_sequence_data(rate, "{", "}", v)}'
|
|
168
|
+
elif value_type == "tuple":
|
|
169
|
+
return "<llmlingua, compress=False>" + f'"{k}": {process_sequence_data(rate, "(", ")", v)}'
|
|
170
|
+
else:
|
|
171
|
+
raise ValueError(f"Invalid value type: {value_type}")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def process_sequence_data(rate, start, end, sequence, is_dict=False):
|
|
175
|
+
res = f'{start}"'
|
|
176
|
+
n = len(sequence)
|
|
177
|
+
if not is_dict:
|
|
178
|
+
for i, item in enumerate(sequence):
|
|
179
|
+
item = str(item)
|
|
180
|
+
res += f"</llmlingua><llmlingua, rate={rate}>{item}</llmlingua><llmlingua, compress=False>"
|
|
181
|
+
if i != n - 1:
|
|
182
|
+
res += '", "'
|
|
183
|
+
else:
|
|
184
|
+
for i, (k, v) in enumerate(sequence.items()):
|
|
185
|
+
item = f"{k}: {v}"
|
|
186
|
+
item.replace('"', "'")
|
|
187
|
+
res += f"</llmlingua><llmlingua, rate={rate}>{item}</llmlingua><llmlingua, compress=False>"
|
|
188
|
+
if i != n - 1:
|
|
189
|
+
res += '", "'
|
|
190
|
+
res += f'"{end}, </llmlingua>'
|
|
191
|
+
return res
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def remove_consecutive_commas(text):
|
|
195
|
+
text = re.sub(r",\s*", ",", text)
|
|
196
|
+
text = re.sub(r",+", ",", text)
|
|
197
|
+
return text
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class PromptCompressor:
|
|
201
|
+
"""
|
|
202
|
+
PromptCompressor is designed for compressing prompts based on a given language model.
|
|
203
|
+
|
|
204
|
+
This class initializes with the language model and its configuration, preparing it for prompt compression tasks.
|
|
205
|
+
The PromptCompressor class is versatile and can be adapted for various models and specific requirements in prompt processing.
|
|
206
|
+
Users can specify different model names and configurations as needed for their particular use case.The architecture is
|
|
207
|
+
based on the paper "LLMLingua: Compressing Prompts for Accelerated Inference of Large Language Models". Jiang, Huiqiang, Qianhui Wu,
|
|
208
|
+
Chin-Yew Lin, Yuqing Yang, and Lili Qiu. arXiv preprint arXiv:2310.05736 (2023).
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
model_name (str, optional): The name of the language model to be loaded. Default is "NousResearch/Llama-2-7b-hf".
|
|
212
|
+
device_map (str, optional): The device to load the model onto, e.g., "cuda" for GPU. Default is "cuda".
|
|
213
|
+
model_config (dict, optional): A dictionary containing the configuration parameters for the model. Default is an empty dictionary.
|
|
214
|
+
open_api_config (dict, optional): A dictionary containing configuration for openai APIs that may be used in conjunction with the model. Default is an empty dictionary.
|
|
215
|
+
use_llmlingua2 (bool, optional): Whether to use llmlingua-2 compressor based on the paper
|
|
216
|
+
"LLMLingua-2: Data Distillation for Efficient and Faithful Task-Agnostic Prompt Compression".
|
|
217
|
+
Zhuoshi Pan, Qianhui Wu, Huiqiang Jiang, Menglin Xia, Xufang Luo, Jue Zhang, Qingwei Lin, Victor Ruhle, Yuqing Yang, Chin-Yew Lin, H. Vicky Zhao, Lili Qiu, Dongmei Zhang.
|
|
218
|
+
arXiv preprint arXiv:2403.2403.12968 (2024), Default is True.
|
|
219
|
+
llmlingua2_config (dict, optional): A dictionary containing the configuration parameters for llmlingua-2. Default is
|
|
220
|
+
{
|
|
221
|
+
"max_batch_size": 50,
|
|
222
|
+
"max_force_token": 100, # max number of the tokens which will be forcely preserved
|
|
223
|
+
}
|
|
224
|
+
Example:
|
|
225
|
+
>>> compress_method = PromptCompressor(model_name="microsoft/llmlingua-2-xlm-roberta-large-meetingbank", use_llmlingua2=True, )
|
|
226
|
+
>>> context = ["This is the first context sentence.", "Here is another context sentence."]
|
|
227
|
+
>>> result = compress_method.compress_prompt(context, use_context_level_filter=True, target_token=5)
|
|
228
|
+
>>> print(result["compressed_prompt"])
|
|
229
|
+
# This will print the compressed version of the context.
|
|
230
|
+
|
|
231
|
+
Note:
|
|
232
|
+
The `PromptCompressor` class requires the Hugging Face Transformers library and an appropriate environment to load and run the models.
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
def __init__(
|
|
236
|
+
self,
|
|
237
|
+
model_name: str = "NousResearch/Llama-2-7b-hf",
|
|
238
|
+
device_map: str = "cuda",
|
|
239
|
+
model_config: dict = {},
|
|
240
|
+
open_api_config: dict = {},
|
|
241
|
+
use_llmlingua2: bool = False,
|
|
242
|
+
llmlingua2_config: dict = {},
|
|
243
|
+
):
|
|
244
|
+
self.model_name = model_name
|
|
245
|
+
self.use_llmlingua2 = use_llmlingua2
|
|
246
|
+
self.retrieval_model = None
|
|
247
|
+
self.retrieval_model_name = None
|
|
248
|
+
self.open_api_config = open_api_config
|
|
249
|
+
self.cache_bos_num = 10
|
|
250
|
+
self.prefix_bos_num = 100
|
|
251
|
+
self.oai_tokenizer = tiktoken.encoding_for_model("gpt-3.5-turbo")
|
|
252
|
+
|
|
253
|
+
self.load_model(model_name, device_map, model_config)
|
|
254
|
+
if use_llmlingua2:
|
|
255
|
+
self.init_llmlingua2(**llmlingua2_config)
|
|
256
|
+
|
|
257
|
+
def init_llmlingua2(
|
|
258
|
+
self,
|
|
259
|
+
max_batch_size: int = 50,
|
|
260
|
+
max_force_token: int = 100,
|
|
261
|
+
):
|
|
262
|
+
self.max_batch_size = max_batch_size
|
|
263
|
+
self.max_seq_len = 512
|
|
264
|
+
self.max_force_token = max_force_token
|
|
265
|
+
self.special_tokens = set(
|
|
266
|
+
[v for k, v in self.tokenizer.special_tokens_map.items() if k != "additional_special_tokens"]
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
self.added_tokens = [f"[NEW{i}]" for i in range(max_force_token)]
|
|
270
|
+
self.tokenizer.add_special_tokens({"additional_special_tokens": self.added_tokens})
|
|
271
|
+
self.model.resize_token_embeddings(len(self.tokenizer))
|
|
272
|
+
|
|
273
|
+
def load_model(self, model_name: str, device_map: str = "cuda", model_config: dict = {}):
|
|
274
|
+
trust_remote_code = model_config.get("trust_remote_code", True)
|
|
275
|
+
if "trust_remote_code" not in model_config:
|
|
276
|
+
model_config["trust_remote_code"] = trust_remote_code
|
|
277
|
+
config = AutoConfig.from_pretrained(model_name, **model_config)
|
|
278
|
+
|
|
279
|
+
MODEL_CLASS = (
|
|
280
|
+
AutoModelForTokenClassification
|
|
281
|
+
if any("ForTokenClassification" in ar for ar in config.architectures)
|
|
282
|
+
else AutoModelForCausalLM
|
|
283
|
+
)
|
|
284
|
+
self.device = device_map if any(key in device_map for key in ["cuda", "cpu", "mps"]) else "cuda"
|
|
285
|
+
model = MODEL_CLASS.from_pretrained(
|
|
286
|
+
model_name,
|
|
287
|
+
torch_dtype=model_config.pop("torch_dtype", "auto" if device_map == "cuda" else torch.float32),
|
|
288
|
+
device_map=device_map,
|
|
289
|
+
config=config,
|
|
290
|
+
ignore_mismatched_sizes=True,
|
|
291
|
+
**model_config,
|
|
292
|
+
)
|
|
293
|
+
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
|
294
|
+
|
|
295
|
+
if model_config.get("pad_to_left", True):
|
|
296
|
+
tokenizer.padding_side = "left"
|
|
297
|
+
tokenizer.pad_token_id = config.pad_token_id if config.pad_token_id else tokenizer.eos_token_id
|
|
298
|
+
self.tokenizer = tokenizer
|
|
299
|
+
self.model = model
|
|
300
|
+
self.context_idxs = []
|
|
301
|
+
self.max_position_embeddings = config.max_position_embeddings
|
|
302
|
+
|
|
303
|
+
def get_ppl(
|
|
304
|
+
self,
|
|
305
|
+
text: str,
|
|
306
|
+
granularity: str = "sentence",
|
|
307
|
+
input_ids=None,
|
|
308
|
+
attention_mask=None,
|
|
309
|
+
past_key_values=None,
|
|
310
|
+
return_kv=False,
|
|
311
|
+
end=None,
|
|
312
|
+
condition_mode: str = "none",
|
|
313
|
+
condition_pos_id: int = 0,
|
|
314
|
+
):
|
|
315
|
+
if input_ids is None:
|
|
316
|
+
tokenized_text = self.tokenizer(text, return_tensors="pt")
|
|
317
|
+
input_ids = tokenized_text["input_ids"].to(self.device)
|
|
318
|
+
attention_mask = tokenized_text["attention_mask"].to(self.device)
|
|
319
|
+
if past_key_values is not None:
|
|
320
|
+
past_length = past_key_values[0][0].shape[2]
|
|
321
|
+
else:
|
|
322
|
+
past_length = 0
|
|
323
|
+
if end is None:
|
|
324
|
+
end = input_ids.shape[1]
|
|
325
|
+
end = min(end, past_length + self.max_position_embeddings)
|
|
326
|
+
with torch.inference_mode(mode=True):
|
|
327
|
+
response = self.model(
|
|
328
|
+
input_ids[:, past_length:end],
|
|
329
|
+
attention_mask=attention_mask[:, :end],
|
|
330
|
+
past_key_values=past_key_values,
|
|
331
|
+
use_cache=True,
|
|
332
|
+
)
|
|
333
|
+
past_key_values = response.past_key_values
|
|
334
|
+
|
|
335
|
+
shift_logits = response.logits[..., :-1, :].contiguous()
|
|
336
|
+
shift_labels = input_ids[..., past_length + 1 : end].contiguous()
|
|
337
|
+
# Flatten the tokens
|
|
338
|
+
active = (attention_mask[:, past_length:end] == 1)[..., :-1].view(-1)
|
|
339
|
+
active_logits = shift_logits.view(-1, shift_logits.size(-1))[active]
|
|
340
|
+
active_labels = shift_labels.view(-1)[active]
|
|
341
|
+
loss_fct = torch.nn.CrossEntropyLoss(reduction="none")
|
|
342
|
+
loss = loss_fct(active_logits, active_labels)
|
|
343
|
+
if condition_mode == "before":
|
|
344
|
+
loss = loss[:condition_pos_id]
|
|
345
|
+
elif condition_mode == "after":
|
|
346
|
+
loss = loss[condition_pos_id:]
|
|
347
|
+
res = loss.mean() if granularity == "sentence" else loss
|
|
348
|
+
return (res, past_key_values) if return_kv else res
|
|
349
|
+
|
|
350
|
+
def __call__(self, *args, **kwargs):
|
|
351
|
+
return self.compress_prompt(*args, **kwargs)
|
|
352
|
+
|
|
353
|
+
def compress_json(
|
|
354
|
+
self,
|
|
355
|
+
json_data: dict,
|
|
356
|
+
json_config: Union[str, dict],
|
|
357
|
+
instruction: str = "",
|
|
358
|
+
question: str = "",
|
|
359
|
+
rate: float = 0.5,
|
|
360
|
+
target_token: float = -1,
|
|
361
|
+
iterative_size: int = 200,
|
|
362
|
+
use_sentence_level_filter: bool = False,
|
|
363
|
+
use_keyvalue_level_filter: bool = False,
|
|
364
|
+
use_token_level_filter: bool = True,
|
|
365
|
+
keep_split: bool = False,
|
|
366
|
+
keep_first_sentence: int = 0,
|
|
367
|
+
keep_last_sentence: int = 0,
|
|
368
|
+
keep_sentence_number: int = 0,
|
|
369
|
+
high_priority_bonus: int = 100,
|
|
370
|
+
context_budget: str = "+100",
|
|
371
|
+
token_budget_ratio: float = 1.4,
|
|
372
|
+
condition_in_question: str = "none",
|
|
373
|
+
reorder_keyvalue: str = "original",
|
|
374
|
+
condition_compare: bool = False,
|
|
375
|
+
rank_method: str = "llmlingua",
|
|
376
|
+
):
|
|
377
|
+
context, force_context_ids = process_structured_json_data(json_data, json_config)
|
|
378
|
+
compressed_res = self.structured_compress_prompt(
|
|
379
|
+
context=context,
|
|
380
|
+
instruction=instruction,
|
|
381
|
+
question=question,
|
|
382
|
+
rate=rate,
|
|
383
|
+
target_token=target_token,
|
|
384
|
+
iterative_size=iterative_size,
|
|
385
|
+
force_context_ids=force_context_ids,
|
|
386
|
+
use_sentence_level_filter=use_sentence_level_filter,
|
|
387
|
+
use_context_level_filter=use_keyvalue_level_filter,
|
|
388
|
+
use_token_level_filter=use_token_level_filter,
|
|
389
|
+
keep_split=keep_split,
|
|
390
|
+
keep_first_sentence=keep_first_sentence,
|
|
391
|
+
keep_last_sentence=keep_last_sentence,
|
|
392
|
+
keep_sentence_number=keep_sentence_number,
|
|
393
|
+
high_priority_bonus=high_priority_bonus,
|
|
394
|
+
context_budget=context_budget,
|
|
395
|
+
token_budget_ratio=token_budget_ratio,
|
|
396
|
+
condition_in_question=condition_in_question,
|
|
397
|
+
reorder_context=reorder_keyvalue,
|
|
398
|
+
condition_compare=condition_compare,
|
|
399
|
+
add_instruction=False,
|
|
400
|
+
rank_method=rank_method,
|
|
401
|
+
concate_question=False,
|
|
402
|
+
strict_preserve_uncompressed=False,
|
|
403
|
+
)
|
|
404
|
+
compressed_json_text = remove_consecutive_commas(compressed_res["compressed_prompt"])
|
|
405
|
+
compressed_res["compressed_prompt"] = json.loads(compressed_json_text)
|
|
406
|
+
return compressed_res
|
|
407
|
+
|
|
408
|
+
def structured_compress_prompt(
|
|
409
|
+
self,
|
|
410
|
+
context: List[str],
|
|
411
|
+
instruction: str = "",
|
|
412
|
+
question: str = "",
|
|
413
|
+
rate: float = 0.5,
|
|
414
|
+
target_token: float = -1,
|
|
415
|
+
iterative_size: int = 200,
|
|
416
|
+
force_context_ids: List[int] = None,
|
|
417
|
+
force_context_number: int = None,
|
|
418
|
+
use_sentence_level_filter: bool = False,
|
|
419
|
+
use_context_level_filter: bool = True,
|
|
420
|
+
use_token_level_filter: bool = True,
|
|
421
|
+
keep_split: bool = False,
|
|
422
|
+
keep_first_sentence: int = 0,
|
|
423
|
+
keep_last_sentence: int = 0,
|
|
424
|
+
keep_sentence_number: int = 0,
|
|
425
|
+
high_priority_bonus: int = 100,
|
|
426
|
+
context_budget: str = "+100",
|
|
427
|
+
token_budget_ratio: float = 1.4,
|
|
428
|
+
condition_in_question: str = "none",
|
|
429
|
+
reorder_context: str = "original",
|
|
430
|
+
dynamic_context_compression_ratio: float = 0.0,
|
|
431
|
+
condition_compare: bool = False,
|
|
432
|
+
add_instruction: bool = False,
|
|
433
|
+
rank_method: str = "llmlingua",
|
|
434
|
+
concate_question: bool = True,
|
|
435
|
+
strict_preserve_uncompressed: bool = True,
|
|
436
|
+
):
|
|
437
|
+
"""
|
|
438
|
+
Compresses the given prompt context based on a specified structure.
|
|
439
|
+
|
|
440
|
+
Each element of context should be segmented using one or more non-nested '<llmlingua></llmlingua>' tags.
|
|
441
|
+
Each '<llmlingua>' tag can include optional parameters 'rate' and 'compress' (e.g., '<llmlingua, rate=0.3, compress=True>'),
|
|
442
|
+
indicating the compression rate for that segment. Default values are 'rate=rate' and 'compress=True'.
|
|
443
|
+
When 'compress' is set to False, it overrides the 'rate' parameter, resulting in no compression for that segment.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
context (List[str]): List of context strings divided by '<llmlingua></llmlingua>' tags with optional compression settings.
|
|
447
|
+
instruction (str, optional): Additional instruction text to be included in the prompt. Default is an empty string.
|
|
448
|
+
question (str, optional): A specific question that the prompt is addressing. Default is an empty string.
|
|
449
|
+
rate (float, optional): The compression rate is defined the same as in paper "Language Modeling Is Compression".
|
|
450
|
+
Delétang, Grégoire, Anian Ruoss, Paul-Ambroise Duquenne, Elliot Catt, Tim Genewein, Christopher Mattern,
|
|
451
|
+
Jordi Grau-Moya et al. "Language modeling is compression." arXiv preprint arXiv:2309.10668 (2023):
|
|
452
|
+
.. math::\text{Compression Rate} = \frac{\text{Compressed Size}}{\text{Raw Size}}
|
|
453
|
+
Default is 0.5. The actual compression rate is generally lower than the specified target, but there can be
|
|
454
|
+
fluctuations due to differences in tokenizers. If specified, it should be a float less than or equal
|
|
455
|
+
to 1.0, representing the target compression rate. ``rate``, is applicable only within the context-level filter
|
|
456
|
+
and the sentence-level filter. In the token-level filter, the rate for each segment overrides the global rate.
|
|
457
|
+
However, for segments where no specific rate is defined, the global rate serves as the default value. The final
|
|
458
|
+
compression rate of the entire text is a composite result of multiple compression rates applied across different sections.
|
|
459
|
+
target_token (float, optional): The global maximum number of tokens to be achieved. Default is -1, indicating no
|
|
460
|
+
specific target. The actual number of tokens after compression should generally be less than the specified target_token,
|
|
461
|
+
but there can be fluctuations due to differences in tokenizers. If specified, compression will be based on the target_token as
|
|
462
|
+
the sole criterion, overriding the ``rate``. ``target_token``, is applicable only within the context-level
|
|
463
|
+
filter and the sentence-level filter. In the token-level filter, the rate for each segment overrides the global target token.
|
|
464
|
+
However, for segments where no specific rate is defined, the global rate calculated from global target token serves
|
|
465
|
+
as the default value. The final target token of the entire text is a composite result of multiple compression rates
|
|
466
|
+
applied across different sections.
|
|
467
|
+
iterative_size (int, optional): The number of tokens to consider in each iteration of compression. Default is 200.
|
|
468
|
+
force_context_ids (List[int], optional): List of specific context IDs to always include in the compressed result. Default is None.
|
|
469
|
+
force_context_number (int, optional): The number of context sections to forcibly include. Default is None.
|
|
470
|
+
use_sentence_level_filter (bool, optional): Whether to apply sentence-level filtering in compression. Default is False.
|
|
471
|
+
use_context_level_filter (bool, optional): Whether to apply context-level filtering in compression. Default is True.
|
|
472
|
+
use_token_level_filter (bool, optional): Whether to apply token-level filtering in compression. Default is True.
|
|
473
|
+
keep_split (bool, optional): Whether to preserve the original separators without compression. Default is False.
|
|
474
|
+
keep_first_sentence (int, optional): Number of sentences to forcibly preserve from the start of the context. Default is 0.
|
|
475
|
+
keep_last_sentence (int, optional): Number of sentences to forcibly preserve from the end of the context. Default is 0.
|
|
476
|
+
keep_sentence_number (int, optional): Total number of sentences to forcibly preserve in the compression. Default is 0.
|
|
477
|
+
high_priority_bonus (int, optional): Bonus score for high-priority sentences to influence their likelihood of being retained. Default is 100.
|
|
478
|
+
context_budget (str, optional): Token budget for the context-level filtering, expressed as a string to indicate flexibility. Default is "+100".
|
|
479
|
+
token_budget_ratio (float, optional): Ratio to adjust token budget during sentence-level filtering. Default is 1.4.
|
|
480
|
+
condition_in_question (str, optional): Specific condition to apply to question in the context. Default is "none".
|
|
481
|
+
reorder_context (str, optional): Strategy for reordering context in the compressed result. Default is "original".
|
|
482
|
+
dynamic_context_compression_ratio (float, optional): Ratio for dynamically adjusting context compression. Default is 0.0.
|
|
483
|
+
condition_compare (bool, optional): Whether to enable condition comparison during token-level compression. Default is False.
|
|
484
|
+
add_instruction (bool, optional): Whether to add the instruction to the prompt prefix. Default is False.
|
|
485
|
+
rank_method (str, optional): Method used for ranking elements during compression. Default is "llmlingua".
|
|
486
|
+
concate_question (bool, optional): Whether to concatenate the question to the compressed prompt. Default is True.
|
|
487
|
+
|
|
488
|
+
Returns:
|
|
489
|
+
dict: A dictionary containing:
|
|
490
|
+
- "compressed_prompt" (str): The resulting compressed prompt.
|
|
491
|
+
- "origin_tokens" (int): The original number of tokens in the input.
|
|
492
|
+
- "compressed_tokens" (int): The number of tokens in the compressed output.
|
|
493
|
+
- "ratio" (str): The compression ratio achieved, calculated as the original token number divided by the token number after compression.
|
|
494
|
+
- "rate" (str): The compression rate achieved, in a human-readable format.
|
|
495
|
+
- "saving" (str): Estimated savings in GPT-4 token usage.
|
|
496
|
+
"""
|
|
497
|
+
if not context:
|
|
498
|
+
context = [" "]
|
|
499
|
+
if isinstance(context, str):
|
|
500
|
+
context = [context]
|
|
501
|
+
context = [self.tokenizer.decode(self.tokenizer(c, add_special_tokens=False).input_ids) for c in context]
|
|
502
|
+
context_tokens_length = [self.get_token_length(c) for c in context]
|
|
503
|
+
instruction_tokens_length, question_tokens_length = self.get_token_length(instruction), self.get_token_length(
|
|
504
|
+
question
|
|
505
|
+
)
|
|
506
|
+
if target_token == -1:
|
|
507
|
+
target_token = (
|
|
508
|
+
(instruction_tokens_length + question_tokens_length + sum(context_tokens_length)) * rate
|
|
509
|
+
- instruction_tokens_length
|
|
510
|
+
- (question_tokens_length if concate_question else 0)
|
|
511
|
+
)
|
|
512
|
+
else:
|
|
513
|
+
rate = target_token / sum(context_tokens_length)
|
|
514
|
+
(
|
|
515
|
+
context,
|
|
516
|
+
context_segs,
|
|
517
|
+
context_segs_rate,
|
|
518
|
+
context_segs_compress,
|
|
519
|
+
) = self.segment_structured_context(context, rate)
|
|
520
|
+
return self.compress_prompt(
|
|
521
|
+
context,
|
|
522
|
+
instruction,
|
|
523
|
+
question,
|
|
524
|
+
rate,
|
|
525
|
+
target_token,
|
|
526
|
+
iterative_size,
|
|
527
|
+
force_context_ids,
|
|
528
|
+
force_context_number,
|
|
529
|
+
use_sentence_level_filter,
|
|
530
|
+
use_context_level_filter,
|
|
531
|
+
use_token_level_filter,
|
|
532
|
+
keep_split,
|
|
533
|
+
keep_first_sentence,
|
|
534
|
+
keep_last_sentence,
|
|
535
|
+
keep_sentence_number,
|
|
536
|
+
high_priority_bonus,
|
|
537
|
+
context_budget,
|
|
538
|
+
token_budget_ratio,
|
|
539
|
+
condition_in_question,
|
|
540
|
+
reorder_context,
|
|
541
|
+
dynamic_context_compression_ratio,
|
|
542
|
+
condition_compare,
|
|
543
|
+
add_instruction,
|
|
544
|
+
rank_method,
|
|
545
|
+
concate_question,
|
|
546
|
+
context_segs=context_segs,
|
|
547
|
+
context_segs_rate=context_segs_rate,
|
|
548
|
+
context_segs_compress=context_segs_compress,
|
|
549
|
+
strict_preserve_uncompressed=strict_preserve_uncompressed,
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
def compress_prompt(
|
|
553
|
+
self,
|
|
554
|
+
context: List[str],
|
|
555
|
+
instruction: str = "",
|
|
556
|
+
question: str = "",
|
|
557
|
+
rate: float = 0.5,
|
|
558
|
+
target_token: float = -1,
|
|
559
|
+
iterative_size: int = 200,
|
|
560
|
+
force_context_ids: List[int] = None,
|
|
561
|
+
force_context_number: int = None,
|
|
562
|
+
use_sentence_level_filter: bool = False,
|
|
563
|
+
use_context_level_filter: bool = True,
|
|
564
|
+
use_token_level_filter: bool = True,
|
|
565
|
+
keep_split: bool = False,
|
|
566
|
+
keep_first_sentence: int = 0,
|
|
567
|
+
keep_last_sentence: int = 0,
|
|
568
|
+
keep_sentence_number: int = 0,
|
|
569
|
+
high_priority_bonus: int = 100,
|
|
570
|
+
context_budget: str = "+100",
|
|
571
|
+
token_budget_ratio: float = 1.4,
|
|
572
|
+
condition_in_question: str = "none",
|
|
573
|
+
reorder_context: str = "original",
|
|
574
|
+
dynamic_context_compression_ratio: float = 0.0,
|
|
575
|
+
condition_compare: bool = False,
|
|
576
|
+
add_instruction: bool = False,
|
|
577
|
+
rank_method: str = "llmlingua",
|
|
578
|
+
concate_question: bool = True,
|
|
579
|
+
context_segs: List[str] = None,
|
|
580
|
+
context_segs_rate: List[float] = None,
|
|
581
|
+
context_segs_compress: List[bool] = None,
|
|
582
|
+
target_context: int = -1,
|
|
583
|
+
context_level_rate: float = 1.0,
|
|
584
|
+
context_level_target_token: int = -1,
|
|
585
|
+
return_word_label: bool = False,
|
|
586
|
+
word_sep: str = "\t\t|\t\t",
|
|
587
|
+
label_sep: str = " ",
|
|
588
|
+
token_to_word: str = "mean",
|
|
589
|
+
force_tokens: List[str] = [],
|
|
590
|
+
force_reserve_digit: bool = False,
|
|
591
|
+
drop_consecutive: bool = False,
|
|
592
|
+
chunk_end_tokens: List[str] = [".", "\n"],
|
|
593
|
+
strict_preserve_uncompressed: bool = True,
|
|
594
|
+
):
|
|
595
|
+
"""
|
|
596
|
+
Compresses the given context.
|
|
597
|
+
|
|
598
|
+
Args:
|
|
599
|
+
context (List[str]): List of context strings that form the basis of the prompt.
|
|
600
|
+
instruction (str, optional): Additional instruction text to be included in the prompt. Default is an empty string.
|
|
601
|
+
question (str, optional): A specific question that the prompt is addressing. Default is an empty string.
|
|
602
|
+
rate (float, optional): The maximum compression rate target to be achieved. The compression rate is defined
|
|
603
|
+
the same as in paper "Language Modeling Is Compression". Delétang, Grégoire, Anian Ruoss, Paul-Ambroise Duquenne,
|
|
604
|
+
Elliot Catt, Tim Genewein, Christopher Mattern, Jordi Grau-Moya et al. "Language modeling is compression."
|
|
605
|
+
arXiv preprint arXiv:2309.10668 (2023):
|
|
606
|
+
.. math::\text{Compression Rate} = \frac{\text{Compressed Size}}{\text{Raw Size}}
|
|
607
|
+
Default is 0.5. The actual compression rate is generally lower than the specified target, but there can be
|
|
608
|
+
fluctuations due to differences in tokenizers. If specified, it should be a float less than or equal
|
|
609
|
+
to 1.0, representing the target compression rate.
|
|
610
|
+
target_token (float, optional): The maximum number of tokens to be achieved. Default is -1, indicating no specific target.
|
|
611
|
+
The actual number of tokens after compression should generally be less than the specified target_token, but there can
|
|
612
|
+
be fluctuations due to differences in tokenizers. If specified, compression will be based on the target_token as
|
|
613
|
+
the sole criterion, overriding the ``rate``.
|
|
614
|
+
iterative_size (int, optional): The number of tokens to consider in each iteration of compression. Default is 200.
|
|
615
|
+
force_context_ids (List[int], optional): List of specific context IDs to always include in the compressed result. Default is None.
|
|
616
|
+
force_context_number (int, optional): The number of context sections to forcibly include. Default is None.
|
|
617
|
+
use_sentence_level_filter (bool, optional): Whether to apply sentence-level filtering in compression. Default is False.
|
|
618
|
+
use_context_level_filter (bool, optional): Whether to apply context-level filtering in compression. Default is True.
|
|
619
|
+
use_token_level_filter (bool, optional): Whether to apply token-level filtering in compression. Default is True.
|
|
620
|
+
keep_split (bool, optional): Whether to preserve the original separators without compression. Default is False.
|
|
621
|
+
keep_first_sentence (int, optional): Number of sentences to forcibly preserve from the start of the context. Default is 0.
|
|
622
|
+
keep_last_sentence (int, optional): Number of sentences to forcibly preserve from the end of the context. Default is 0.
|
|
623
|
+
keep_sentence_number (int, optional): Total number of sentences to forcibly preserve in the compression. Default is 0.
|
|
624
|
+
high_priority_bonus (int, optional): Bonus score for high-priority sentences to influence their likelihood of being retained. Default is 100.
|
|
625
|
+
context_budget (str, optional): Token budget for the context-level filtering, expressed as a string to indicate flexibility. Default is "+100".
|
|
626
|
+
token_budget_ratio (float, optional): Ratio to adjust token budget during sentence-level filtering. Default is 1.4.
|
|
627
|
+
condition_in_question (str, optional): Specific condition to apply to question in the context. Default is "none".
|
|
628
|
+
reorder_context (str, optional): Strategy for reordering context in the compressed result. Default is "original".
|
|
629
|
+
dynamic_context_compression_ratio (float, optional): Ratio for dynamically adjusting context compression. Default is 0.0.
|
|
630
|
+
condition_compare (bool, optional): Whether to enable condition comparison during token-level compression. Default is False.
|
|
631
|
+
add_instruction (bool, optional): Whether to add the instruction to the prompt prefix. Default is False.
|
|
632
|
+
rank_method (str, optional): Method used for ranking elements during compression. Default is "llmlingua".
|
|
633
|
+
concate_question (bool, optional): Whether to concatenate the question to the compressed prompt. Default is True.
|
|
634
|
+
|
|
635
|
+
target_context (int, optional): The maximum number of contexts to be achieved. Default is -1, indicating no specific target.
|
|
636
|
+
context_level_rate (float, optional): The minimum compression rate target to be achieved in context level. Default is 1.0.
|
|
637
|
+
context_level_target_token (float, optional): The maximum number of tokens to be achieved in context level compression.
|
|
638
|
+
Default is -1, indicating no specific target. Only used in the coarse-to-fine compression senario.
|
|
639
|
+
force_context_ids (List[int], optional): List of specific context IDs to always include in the compressed result. Default is None.
|
|
640
|
+
return_word_label (bool, optional): Whether to return word with corresponding label. Default is False.
|
|
641
|
+
word_sep (str, optional): The sep token used in fn_labeled_original_prompt to partition words. Default is "\t\t|\t\t".
|
|
642
|
+
label_sep (str, optional): The sep token used in fn_labeled_original_prompt to partition word and label. Default is " ".
|
|
643
|
+
token_to_word (str, optional): How to convert token probability to word probability. Default is "mean".
|
|
644
|
+
force_tokens (List[str], optional): List of specific tokens to always include in the compressed result. Default is [].
|
|
645
|
+
force_reserve_digit (bool, optional): Whether to forcibly reserve tokens that containing digit (0,...,9). Default is False.
|
|
646
|
+
drop_consecutive (bool, optinal): Whether to drop tokens which are in 'force_tokens' but appears consecutively in compressed prompt.
|
|
647
|
+
Default is False.
|
|
648
|
+
chunk_end_tokens (List[str], optinal): The early stop tokens for segmenting chunk. Default is [".", "\n"],
|
|
649
|
+
Returns:
|
|
650
|
+
dict: A dictionary containing:
|
|
651
|
+
- "compressed_prompt" (str): The resulting compressed prompt.
|
|
652
|
+
- "compressed_prompt_list" (List[str]): List of the resulting compressed prompt. Only used in llmlingua2.
|
|
653
|
+
- "fn_labeled_original_prompt" (str): original words along with their labels
|
|
654
|
+
indicating whether to reserve in compressed prompt, in the format (word label_sep label)
|
|
655
|
+
Only used in llmlingua2 when return_word_label = True.
|
|
656
|
+
- "origin_tokens" (int): The original number of tokens in the input.
|
|
657
|
+
- "compressed_tokens" (int): The number of tokens in the compressed output.
|
|
658
|
+
- "ratio" (str): The compression ratio achieved, calculated as the original token number divided by the token number after compression.
|
|
659
|
+
- "rate" (str): The compression rate achieved, in a human-readable format.
|
|
660
|
+
- "saving" (str): Estimated savings in GPT-4 token usage.
|
|
661
|
+
"""
|
|
662
|
+
if self.use_llmlingua2:
|
|
663
|
+
return self.compress_prompt_llmlingua2(
|
|
664
|
+
context,
|
|
665
|
+
rate=rate,
|
|
666
|
+
target_token=target_token,
|
|
667
|
+
use_context_level_filter=use_context_level_filter,
|
|
668
|
+
use_token_level_filter=use_token_level_filter,
|
|
669
|
+
target_context=target_context,
|
|
670
|
+
context_level_rate=context_level_rate,
|
|
671
|
+
context_level_target_token=context_level_target_token,
|
|
672
|
+
force_context_ids=force_context_ids,
|
|
673
|
+
return_word_label=return_word_label,
|
|
674
|
+
word_sep=word_sep,
|
|
675
|
+
label_sep=label_sep,
|
|
676
|
+
token_to_word=token_to_word,
|
|
677
|
+
force_tokens=force_tokens,
|
|
678
|
+
force_reserve_digit=force_reserve_digit,
|
|
679
|
+
drop_consecutive=drop_consecutive,
|
|
680
|
+
chunk_end_tokens=chunk_end_tokens,
|
|
681
|
+
)
|
|
682
|
+
assert (
|
|
683
|
+
rate <= 1.0
|
|
684
|
+
), "Error: 'rate' must not exceed 1.0. The value of 'rate' indicates compression rate and must be within the range [0, 1]."
|
|
685
|
+
|
|
686
|
+
if not context:
|
|
687
|
+
context = [" "]
|
|
688
|
+
if isinstance(context, str):
|
|
689
|
+
context = [context]
|
|
690
|
+
assert not (
|
|
691
|
+
rank_method == "longllmlingua" and not question
|
|
692
|
+
), "In the LongLLMLingua, it is necessary to set a question."
|
|
693
|
+
if condition_compare and "_condition" not in condition_in_question:
|
|
694
|
+
condition_in_question += "_condition"
|
|
695
|
+
if rank_method == "longllmlingua":
|
|
696
|
+
if condition_in_question == "none":
|
|
697
|
+
condition_in_question = "after"
|
|
698
|
+
elif rank_method == "llmlingua":
|
|
699
|
+
condition_in_question = "none" if "_condition" not in condition_in_question else "none_condition"
|
|
700
|
+
origin_tokens = len(self.oai_tokenizer.encode("\n\n".join([instruction] + context + [question]).strip()))
|
|
701
|
+
context_tokens_length = [self.get_token_length(c) for c in context]
|
|
702
|
+
instruction_tokens_length, question_tokens_length = self.get_token_length(instruction), self.get_token_length(
|
|
703
|
+
question
|
|
704
|
+
)
|
|
705
|
+
if target_token == -1:
|
|
706
|
+
target_token = (
|
|
707
|
+
(instruction_tokens_length + question_tokens_length + sum(context_tokens_length)) * rate
|
|
708
|
+
- instruction_tokens_length
|
|
709
|
+
- (question_tokens_length if concate_question else 0)
|
|
710
|
+
)
|
|
711
|
+
condition_flag = "_condition" in condition_in_question
|
|
712
|
+
condition_in_question = condition_in_question.replace("_condition", "")
|
|
713
|
+
|
|
714
|
+
if len(context) > 1 and use_context_level_filter:
|
|
715
|
+
context, dynamic_ratio, context_used = self.control_context_budget(
|
|
716
|
+
context,
|
|
717
|
+
context_tokens_length,
|
|
718
|
+
target_token,
|
|
719
|
+
force_context_ids,
|
|
720
|
+
force_context_number,
|
|
721
|
+
question,
|
|
722
|
+
condition_in_question,
|
|
723
|
+
reorder_context=reorder_context,
|
|
724
|
+
dynamic_context_compression_ratio=dynamic_context_compression_ratio,
|
|
725
|
+
rank_method=rank_method,
|
|
726
|
+
context_budget=context_budget,
|
|
727
|
+
context_segs=context_segs,
|
|
728
|
+
context_segs_rate=context_segs_rate,
|
|
729
|
+
context_segs_compress=context_segs_compress,
|
|
730
|
+
strict_preserve_uncompressed=strict_preserve_uncompressed,
|
|
731
|
+
)
|
|
732
|
+
if context_segs is not None:
|
|
733
|
+
context_segs = [context_segs[idx] for idx in context_used]
|
|
734
|
+
context_segs_rate = [context_segs_rate[idx] for idx in context_used]
|
|
735
|
+
context_segs_compress = [context_segs_compress[idx] for idx in context_used]
|
|
736
|
+
else:
|
|
737
|
+
dynamic_ratio = [0.0] * len(context)
|
|
738
|
+
|
|
739
|
+
segments_info = []
|
|
740
|
+
if use_sentence_level_filter:
|
|
741
|
+
context, segments_info = self.control_sentence_budget(
|
|
742
|
+
context,
|
|
743
|
+
target_token,
|
|
744
|
+
keep_first_sentence=keep_first_sentence,
|
|
745
|
+
keep_last_sentence=keep_last_sentence,
|
|
746
|
+
keep_sentence_number=keep_sentence_number,
|
|
747
|
+
high_priority_bonus=high_priority_bonus,
|
|
748
|
+
token_budget_ratio=token_budget_ratio,
|
|
749
|
+
question=question,
|
|
750
|
+
condition_in_question=condition_in_question,
|
|
751
|
+
rank_method=rank_method,
|
|
752
|
+
context_segs=context_segs,
|
|
753
|
+
context_segs_rate=context_segs_rate,
|
|
754
|
+
context_segs_compress=context_segs_compress,
|
|
755
|
+
)
|
|
756
|
+
elif context_segs is not None:
|
|
757
|
+
for context_idx in range(len(context)):
|
|
758
|
+
segments_info.append(
|
|
759
|
+
[
|
|
760
|
+
(len(seg_text), seg_rate, seg_compress)
|
|
761
|
+
for seg_text, seg_rate, seg_compress in zip(
|
|
762
|
+
context_segs[context_idx],
|
|
763
|
+
context_segs_rate[context_idx],
|
|
764
|
+
context_segs_compress[context_idx],
|
|
765
|
+
)
|
|
766
|
+
]
|
|
767
|
+
)
|
|
768
|
+
segments_info = [self.concate_segment_info(segment_info) for segment_info in segments_info]
|
|
769
|
+
|
|
770
|
+
if condition_flag:
|
|
771
|
+
prefix = question + "\n\n" + instruction if add_instruction else question
|
|
772
|
+
if self.get_token_length(prefix + "\n\n") + iterative_size * 2 > self.max_position_embeddings:
|
|
773
|
+
tokens = self.tokenizer(prefix, add_special_tokens=False).input_ids
|
|
774
|
+
prefix = self.tokenizer.decode(
|
|
775
|
+
tokens[: self.prefix_bos_num]
|
|
776
|
+
+ tokens[
|
|
777
|
+
len(tokens) - self.max_position_embeddings + 2 + self.prefix_bos_num + 2 * iterative_size :
|
|
778
|
+
]
|
|
779
|
+
)
|
|
780
|
+
start = self.get_prefix_length(prefix + "\n\n", context[0])
|
|
781
|
+
context = [prefix] + context
|
|
782
|
+
else:
|
|
783
|
+
start = 0
|
|
784
|
+
|
|
785
|
+
if use_token_level_filter:
|
|
786
|
+
context = self.iterative_compress_prompt(
|
|
787
|
+
context,
|
|
788
|
+
target_token,
|
|
789
|
+
iterative_size=iterative_size,
|
|
790
|
+
keep_split=keep_split,
|
|
791
|
+
start=start,
|
|
792
|
+
dynamic_ratio=dynamic_ratio,
|
|
793
|
+
condition_compare=condition_compare,
|
|
794
|
+
segments_info=segments_info,
|
|
795
|
+
)
|
|
796
|
+
compressed_prompt = self.tokenizer.batch_decode(context[0])[0].replace("<s> ", "").replace("<s>", "")
|
|
797
|
+
else:
|
|
798
|
+
if condition_flag:
|
|
799
|
+
context = context[1:]
|
|
800
|
+
compressed_prompt = "\n\n".join(context)
|
|
801
|
+
|
|
802
|
+
res = []
|
|
803
|
+
if instruction:
|
|
804
|
+
res.append(instruction)
|
|
805
|
+
if compressed_prompt.strip():
|
|
806
|
+
res.append(compressed_prompt)
|
|
807
|
+
if question and concate_question:
|
|
808
|
+
res.append(question)
|
|
809
|
+
|
|
810
|
+
compressed_prompt = "\n\n".join(res)
|
|
811
|
+
|
|
812
|
+
compressed_tokens = len(self.oai_tokenizer.encode(compressed_prompt))
|
|
813
|
+
saving = (origin_tokens - compressed_tokens) * 0.06 / 1000
|
|
814
|
+
ratio = 1 if compressed_tokens == 0 else origin_tokens / compressed_tokens
|
|
815
|
+
rate = 1 / ratio
|
|
816
|
+
return {
|
|
817
|
+
"compressed_prompt": compressed_prompt,
|
|
818
|
+
"origin_tokens": origin_tokens,
|
|
819
|
+
"compressed_tokens": compressed_tokens,
|
|
820
|
+
"ratio": f"{ratio:.1f}x",
|
|
821
|
+
"rate": f"{rate * 100:.1f}%",
|
|
822
|
+
"saving": f", Saving ${saving:.1f} in GPT-4.",
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
def compress_prompt_llmlingua2(
|
|
826
|
+
self,
|
|
827
|
+
context: List[str],
|
|
828
|
+
rate: float = 0.5,
|
|
829
|
+
target_token: int = -1,
|
|
830
|
+
use_context_level_filter: bool = False,
|
|
831
|
+
use_token_level_filter: bool = True,
|
|
832
|
+
target_context: int = -1,
|
|
833
|
+
context_level_rate: float = 1.0,
|
|
834
|
+
context_level_target_token: int = -1,
|
|
835
|
+
force_context_ids: List[int] = [],
|
|
836
|
+
return_word_label: bool = False,
|
|
837
|
+
word_sep: str = "\t\t|\t\t",
|
|
838
|
+
label_sep: str = " ",
|
|
839
|
+
token_to_word: str = "mean",
|
|
840
|
+
force_tokens: List[str] = [],
|
|
841
|
+
force_reserve_digit: bool = False,
|
|
842
|
+
drop_consecutive: bool = False,
|
|
843
|
+
chunk_end_tokens: List[str] = [".", "\n"],
|
|
844
|
+
):
|
|
845
|
+
"""
|
|
846
|
+
Compresses the given context, instruction and question.
|
|
847
|
+
|
|
848
|
+
Args:
|
|
849
|
+
context (List[str]): List of context strings that form the basis of the prompt.
|
|
850
|
+
rate (float, optional): The minimum compression rate target to be achieved. Default is 0.5. The actual compression rate
|
|
851
|
+
generally exceeds the specified target, but there can be fluctuations due to differences in tokenizers. If specified,
|
|
852
|
+
it should be a float greater than or equal to 1.0, representing the target compression rate.
|
|
853
|
+
target_token (int, optional): The maximum number of tokens to be achieved. Default is -1, indicating no specific target.
|
|
854
|
+
The actual number of tokens after compression should generally be less than the specified target_token, but there can
|
|
855
|
+
be fluctuations due to differences in tokenizers. If specified, compression will be based on the target_token as
|
|
856
|
+
the sole criterion, overriding the rate.
|
|
857
|
+
target_context (int, optional): The maximum number of contexts to be achieved. Default is -1, indicating no specific target.
|
|
858
|
+
Only used in the coarse-to-fine compression.
|
|
859
|
+
context_level_rate (float, optional): The minimum compression rate target to be achieved in context level. Default is 1.0.
|
|
860
|
+
Only used in the coarse-to-fine compression.
|
|
861
|
+
context_level_target_token (float, optional): The maximum number of tokens to be achieved in context level compression.
|
|
862
|
+
Default is -1, indicating no specific target. Only used in the coarse-to-fine compression senario.
|
|
863
|
+
force_context_ids (List[int], optional): List of specific context IDs to always include in the compressed result. Default is None.
|
|
864
|
+
return_word_label (bool, optional): Whether to return word with corresponding label. Default is False.
|
|
865
|
+
word_sep (str, optional): The sep token used in fn_labeled_original_prompt to partition words. Default is "\t\t|\t\t".
|
|
866
|
+
label_sep (str, optional): The sep token used in fn_labeled_original_prompt to partition word and label. Default is " ".
|
|
867
|
+
token_to_word (str, optional): How to convert token probability to word probability. Default is "mean".
|
|
868
|
+
force_tokens (List[str], optional): List of specific tokens to always include in the compressed result. Default is [].
|
|
869
|
+
force_reserve_digit (bool, optional): Whether to forcibly reserve tokens that containing digit (0,...,9). Default is False.
|
|
870
|
+
drop_consecutive (bool, optinal): Whether to drop tokens which are in 'force_tokens' but appears consecutively in compressed prompt.
|
|
871
|
+
Default is False.
|
|
872
|
+
chunk_end_tokens (List[str], optional): The early stop tokens for segmenting chunk. Default is [".", "\n"].
|
|
873
|
+
Returns:
|
|
874
|
+
dict: A dictionary containing:
|
|
875
|
+
- "compressed_prompt" (str): The resulting compressed prompt.
|
|
876
|
+
- "compressed_prompt_list" (List[str]): List of the resulting compressed prompt.
|
|
877
|
+
- "fn_labeled_original_prompt" (str): original words along with their labels
|
|
878
|
+
indicating whether to reserve in compressed prompt, in the format (word label_sep label)
|
|
879
|
+
- "origin_tokens" (int): The original number of tokens in the input.
|
|
880
|
+
- "compressed_tokens" (int): The number of tokens in the compressed output.
|
|
881
|
+
- "ratio" (str): The compression ratio achieved, in a human-readable format.
|
|
882
|
+
- "rate" (str): The compression rate achieved, in a human-readable format.
|
|
883
|
+
- "saving" (str): Estimated savings in GPT-4 token usage.
|
|
884
|
+
|
|
885
|
+
"""
|
|
886
|
+
assert len(force_tokens) <= self.max_force_token
|
|
887
|
+
token_map = {}
|
|
888
|
+
for i, t in enumerate(force_tokens):
|
|
889
|
+
if len(self.tokenizer.tokenize(t)) != 1:
|
|
890
|
+
token_map[t] = self.added_tokens[i]
|
|
891
|
+
chunk_end_tokens = copy.deepcopy(chunk_end_tokens)
|
|
892
|
+
for c in chunk_end_tokens:
|
|
893
|
+
if c in token_map:
|
|
894
|
+
chunk_end_tokens.append(token_map[c])
|
|
895
|
+
chunk_end_tokens = set(chunk_end_tokens)
|
|
896
|
+
|
|
897
|
+
if type(context) == str:
|
|
898
|
+
context = [context]
|
|
899
|
+
context = copy.deepcopy(context)
|
|
900
|
+
|
|
901
|
+
if len(context) == 1 and use_context_level_filter:
|
|
902
|
+
use_context_level_filter = False
|
|
903
|
+
|
|
904
|
+
n_original_token = 0
|
|
905
|
+
context_chunked = []
|
|
906
|
+
for i in range(len(context)):
|
|
907
|
+
n_original_token += self.get_token_length(context[i], use_oai_tokenizer=True)
|
|
908
|
+
for ori_token, new_token in token_map.items():
|
|
909
|
+
context[i] = context[i].replace(ori_token, new_token)
|
|
910
|
+
context_chunked.append(self.__chunk_context(context[i], chunk_end_tokens=chunk_end_tokens))
|
|
911
|
+
|
|
912
|
+
if use_context_level_filter:
|
|
913
|
+
# want use_context_level_filter but do not specify any parameters in context level?
|
|
914
|
+
# we will set context_level_rate = (rate + 1.0) / 2 if specify rate or target_token * 2 if specify target_token
|
|
915
|
+
if target_context <= 0 and context_level_rate >= 1.0 and context_level_target_token <= 0:
|
|
916
|
+
if target_token < 0 and rate < 1.0:
|
|
917
|
+
context_level_rate = (rate + 1.0) / 2 if use_token_level_filter else rate
|
|
918
|
+
if target_token >= 0:
|
|
919
|
+
context_level_target_token = target_token * 2 if use_token_level_filter else target_token
|
|
920
|
+
|
|
921
|
+
if target_context >= 0:
|
|
922
|
+
context_level_rate = min(target_context / len(context), 1.0)
|
|
923
|
+
if context_level_target_token >= 0:
|
|
924
|
+
context_level_rate = min(context_level_target_token / n_original_token, 1.0)
|
|
925
|
+
|
|
926
|
+
context_probs, context_words = self.__get_context_prob(
|
|
927
|
+
context_chunked,
|
|
928
|
+
token_to_word=token_to_word,
|
|
929
|
+
force_tokens=force_tokens,
|
|
930
|
+
token_map=token_map,
|
|
931
|
+
force_reserve_digit=force_reserve_digit,
|
|
932
|
+
)
|
|
933
|
+
|
|
934
|
+
threshold = np.percentile(context_probs, int(100 * (1 - context_level_rate)))
|
|
935
|
+
|
|
936
|
+
reserved_context = []
|
|
937
|
+
context_label = [False] * len(context_probs)
|
|
938
|
+
for i, p in enumerate(context_probs):
|
|
939
|
+
if p >= threshold or (force_context_ids is not None and i in force_context_ids):
|
|
940
|
+
reserved_context.append(context_chunked[i])
|
|
941
|
+
context_label[i] = True
|
|
942
|
+
n_reserved_token = 0
|
|
943
|
+
for chunks in reserved_context:
|
|
944
|
+
for c in chunks:
|
|
945
|
+
n_reserved_token += self.get_token_length(c, use_oai_tokenizer=True)
|
|
946
|
+
if target_token >= 0:
|
|
947
|
+
rate = min(target_token / n_reserved_token, 1.0)
|
|
948
|
+
|
|
949
|
+
if use_token_level_filter:
|
|
950
|
+
compressed_context, word_list, word_label_list = self.__compress(
|
|
951
|
+
reserved_context,
|
|
952
|
+
reduce_rate=max(0, 1 - rate),
|
|
953
|
+
token_to_word=token_to_word,
|
|
954
|
+
force_tokens=force_tokens,
|
|
955
|
+
token_map=token_map,
|
|
956
|
+
force_reserve_digit=force_reserve_digit,
|
|
957
|
+
drop_consecutive=drop_consecutive,
|
|
958
|
+
)
|
|
959
|
+
else:
|
|
960
|
+
compressed_context, word_list, word_label_list = self.__compress(
|
|
961
|
+
reserved_context,
|
|
962
|
+
reduce_rate=0,
|
|
963
|
+
token_to_word=token_to_word,
|
|
964
|
+
force_tokens=force_tokens,
|
|
965
|
+
token_map=token_map,
|
|
966
|
+
force_reserve_digit=force_reserve_digit,
|
|
967
|
+
drop_consecutive=drop_consecutive,
|
|
968
|
+
)
|
|
969
|
+
|
|
970
|
+
n_compressed_token = 0
|
|
971
|
+
for c in compressed_context:
|
|
972
|
+
n_compressed_token += self.get_token_length(c, use_oai_tokenizer=True)
|
|
973
|
+
saving = (n_original_token - n_compressed_token) * 0.06 / 1000
|
|
974
|
+
ratio = 1 if n_compressed_token == 0 else n_original_token / n_compressed_token
|
|
975
|
+
res = {
|
|
976
|
+
"compressed_prompt": "\n\n".join(compressed_context),
|
|
977
|
+
"compressed_prompt_list": compressed_context,
|
|
978
|
+
"origin_tokens": n_original_token,
|
|
979
|
+
"compressed_tokens": n_compressed_token,
|
|
980
|
+
"ratio": f"{ratio:.1f}x",
|
|
981
|
+
"rate": f"{1 / ratio * 100:.1f}%",
|
|
982
|
+
"saving": f", Saving ${saving:.1f} in GPT-4.",
|
|
983
|
+
}
|
|
984
|
+
if return_word_label:
|
|
985
|
+
words = []
|
|
986
|
+
labels = []
|
|
987
|
+
j = 0
|
|
988
|
+
for i in range(len(context)):
|
|
989
|
+
if context_label[i]:
|
|
990
|
+
words.extend(word_list[j])
|
|
991
|
+
labels.extend(word_label_list[j])
|
|
992
|
+
j += 1
|
|
993
|
+
else:
|
|
994
|
+
words.extend(context_words[i])
|
|
995
|
+
labels.extend([0] * len(context_words[i]))
|
|
996
|
+
word_label_lines = word_sep.join([f"{word}{label_sep}{label}" for word, label in zip(words, labels)])
|
|
997
|
+
res["fn_labeled_original_prompt"] = word_label_lines
|
|
998
|
+
return res
|
|
999
|
+
|
|
1000
|
+
if target_token > 0:
|
|
1001
|
+
rate = min(target_token / n_original_token, 1.0)
|
|
1002
|
+
|
|
1003
|
+
if use_token_level_filter:
|
|
1004
|
+
compressed_context, word_list, word_label_list = self.__compress(
|
|
1005
|
+
context_chunked,
|
|
1006
|
+
reduce_rate=max(0, 1 - rate),
|
|
1007
|
+
token_to_word=token_to_word,
|
|
1008
|
+
force_tokens=force_tokens,
|
|
1009
|
+
token_map=token_map,
|
|
1010
|
+
force_reserve_digit=force_reserve_digit,
|
|
1011
|
+
drop_consecutive=drop_consecutive,
|
|
1012
|
+
)
|
|
1013
|
+
else:
|
|
1014
|
+
compressed_context, word_list, word_label_list = self.__compress(
|
|
1015
|
+
context_chunked,
|
|
1016
|
+
reduce_rate=0,
|
|
1017
|
+
token_to_word=token_to_word,
|
|
1018
|
+
force_tokens=force_tokens,
|
|
1019
|
+
token_map=token_map,
|
|
1020
|
+
force_reserve_digit=force_reserve_digit,
|
|
1021
|
+
drop_consecutive=drop_consecutive,
|
|
1022
|
+
)
|
|
1023
|
+
|
|
1024
|
+
n_compressed_token = 0
|
|
1025
|
+
for c in compressed_context:
|
|
1026
|
+
n_compressed_token += self.get_token_length(c, use_oai_tokenizer=True)
|
|
1027
|
+
saving = (n_original_token - n_compressed_token) * 0.06 / 1000
|
|
1028
|
+
ratio = 1 if n_compressed_token == 0 else n_original_token / n_compressed_token
|
|
1029
|
+
res = {
|
|
1030
|
+
"compressed_prompt": "\n\n".join(compressed_context),
|
|
1031
|
+
"compressed_prompt_list": compressed_context,
|
|
1032
|
+
"origin_tokens": n_original_token,
|
|
1033
|
+
"compressed_tokens": n_compressed_token,
|
|
1034
|
+
"ratio": f"{ratio:.1f}x",
|
|
1035
|
+
"rate": f"{1 / ratio * 100:.1f}%",
|
|
1036
|
+
"saving": f", Saving ${saving:.1f} in GPT-4.",
|
|
1037
|
+
}
|
|
1038
|
+
if return_word_label:
|
|
1039
|
+
words = []
|
|
1040
|
+
labels = []
|
|
1041
|
+
for w_list, l_list in zip(word_list, word_label_list):
|
|
1042
|
+
words.extend(w_list)
|
|
1043
|
+
labels.extend(l_list)
|
|
1044
|
+
|
|
1045
|
+
word_label_lines = word_sep.join([f"{word}{label_sep}{label}" for word, label in zip(words, labels)])
|
|
1046
|
+
res["fn_labeled_original_prompt"] = word_label_lines
|
|
1047
|
+
return res
|
|
1048
|
+
|
|
1049
|
+
def get_token_length(
|
|
1050
|
+
self,
|
|
1051
|
+
text: str,
|
|
1052
|
+
add_special_tokens: bool = True,
|
|
1053
|
+
use_oai_tokenizer: bool = False,
|
|
1054
|
+
):
|
|
1055
|
+
if use_oai_tokenizer:
|
|
1056
|
+
return len(self.oai_tokenizer.encode(text))
|
|
1057
|
+
else:
|
|
1058
|
+
return len(self.tokenizer(text, add_special_tokens=add_special_tokens).input_ids)
|
|
1059
|
+
|
|
1060
|
+
def get_prefix_length(self, prefix: str, text: str):
|
|
1061
|
+
possible_prefix_token = max(self.get_token_length(prefix, False) - 3, 1)
|
|
1062
|
+
full_input_ids = self.tokenizer(prefix + text[:100], add_special_tokens=False).input_ids
|
|
1063
|
+
for i in range(possible_prefix_token, len(full_input_ids)):
|
|
1064
|
+
cur_prefix = self.tokenizer.decode(full_input_ids[:i])
|
|
1065
|
+
if cur_prefix == prefix:
|
|
1066
|
+
break
|
|
1067
|
+
assert self.tokenizer.decode(full_input_ids[i:]) == text[:100]
|
|
1068
|
+
return i
|
|
1069
|
+
|
|
1070
|
+
def get_condition_ppl(
|
|
1071
|
+
self,
|
|
1072
|
+
text: str,
|
|
1073
|
+
question: str,
|
|
1074
|
+
condition_in_question: str = "none",
|
|
1075
|
+
granularity: str = "sentence",
|
|
1076
|
+
):
|
|
1077
|
+
if condition_in_question == "none":
|
|
1078
|
+
return self.get_ppl(text, granularity=granularity)
|
|
1079
|
+
elif condition_in_question == "before":
|
|
1080
|
+
return self.get_ppl(
|
|
1081
|
+
question + text,
|
|
1082
|
+
granularity=granularity,
|
|
1083
|
+
condition_mode="after",
|
|
1084
|
+
condition_pos_id=self.get_token_length(question) - 1,
|
|
1085
|
+
)
|
|
1086
|
+
elif condition_in_question == "after":
|
|
1087
|
+
return self.get_ppl(
|
|
1088
|
+
text + question,
|
|
1089
|
+
granularity=granularity,
|
|
1090
|
+
condition_mode="after",
|
|
1091
|
+
condition_pos_id=self.get_token_length(text) - 1,
|
|
1092
|
+
)
|
|
1093
|
+
|
|
1094
|
+
def get_dynamic_compression_ratio(
|
|
1095
|
+
self,
|
|
1096
|
+
context: list,
|
|
1097
|
+
target_token: float,
|
|
1098
|
+
iterative_size: int,
|
|
1099
|
+
dynamic_ratio: list,
|
|
1100
|
+
start: int,
|
|
1101
|
+
seg_info: List[List[tuple]] = None,
|
|
1102
|
+
):
|
|
1103
|
+
def get_ratio(base: float, delta: float):
|
|
1104
|
+
return max(min(1, base + delta), 0)
|
|
1105
|
+
|
|
1106
|
+
context_length = [self.get_token_length(ii, False) + 2 for ii in context]
|
|
1107
|
+
if start:
|
|
1108
|
+
context_length = context_length[1:]
|
|
1109
|
+
tau = target_token / (sum(context_length) + 1)
|
|
1110
|
+
res, idx, last, last_target = [], 0, 1, []
|
|
1111
|
+
while idx < len(context_length):
|
|
1112
|
+
if last + context_length[idx] >= iterative_size:
|
|
1113
|
+
last_target.append((iterative_size - last, get_ratio(tau, dynamic_ratio[idx])))
|
|
1114
|
+
res.append(last_target)
|
|
1115
|
+
last = last + context_length[idx] - iterative_size
|
|
1116
|
+
if last > iterative_size:
|
|
1117
|
+
k = last // iterative_size
|
|
1118
|
+
res.extend([[(iterative_size, get_ratio(tau, dynamic_ratio[idx]))]] * k)
|
|
1119
|
+
last -= k * iterative_size
|
|
1120
|
+
|
|
1121
|
+
last_target = [(last, get_ratio(tau, dynamic_ratio[idx]))] if last else []
|
|
1122
|
+
else:
|
|
1123
|
+
last += context_length[idx]
|
|
1124
|
+
last_target.append((context_length[idx], get_ratio(tau, dynamic_ratio[idx])))
|
|
1125
|
+
idx += 1
|
|
1126
|
+
if last_target:
|
|
1127
|
+
res.append(last_target)
|
|
1128
|
+
return res
|
|
1129
|
+
|
|
1130
|
+
def get_structured_dynamic_compression_ratio(
|
|
1131
|
+
self,
|
|
1132
|
+
context: list,
|
|
1133
|
+
iterative_size: int,
|
|
1134
|
+
dynamic_ratio: list,
|
|
1135
|
+
start: int,
|
|
1136
|
+
seg_info: List[List[tuple]] = None,
|
|
1137
|
+
):
|
|
1138
|
+
if start:
|
|
1139
|
+
pure_context = context[1:]
|
|
1140
|
+
else:
|
|
1141
|
+
pure_context = context
|
|
1142
|
+
global_dynamic_rate, global_dynamic_compress, segments = [], [], []
|
|
1143
|
+
for context_idx, text in enumerate(pure_context):
|
|
1144
|
+
text_seen = 0
|
|
1145
|
+
for seg_idx, (seg_len, seg_rate, seg_compress) in enumerate(seg_info[context_idx]):
|
|
1146
|
+
seg_text = text[text_seen : text_seen + seg_len]
|
|
1147
|
+
if seg_idx == len(seg_info[context_idx]) - 1 and context_idx != len(pure_context) - 1:
|
|
1148
|
+
seg_text += "\n\n"
|
|
1149
|
+
segments.append(seg_text)
|
|
1150
|
+
if seg_compress:
|
|
1151
|
+
global_dynamic_rate.append(seg_rate)
|
|
1152
|
+
else:
|
|
1153
|
+
global_dynamic_rate.append(1.0)
|
|
1154
|
+
global_dynamic_compress.append(seg_compress)
|
|
1155
|
+
text_seen += seg_len
|
|
1156
|
+
origin_text = "\n\n".join(pure_context)
|
|
1157
|
+
assert len("".join(segments)) == len(origin_text)
|
|
1158
|
+
assert len(segments) == len(global_dynamic_rate) == len(global_dynamic_compress)
|
|
1159
|
+
|
|
1160
|
+
text_input_ids = self.tokenizer("\n\n".join(context), add_special_tokens=False).input_ids[start:]
|
|
1161
|
+
assert self.tokenizer.decode(text_input_ids) == origin_text
|
|
1162
|
+
dynamic_compression_ratio = self.token_segment(
|
|
1163
|
+
text_input_ids,
|
|
1164
|
+
iterative_size,
|
|
1165
|
+
segments,
|
|
1166
|
+
global_dynamic_rate,
|
|
1167
|
+
global_dynamic_compress,
|
|
1168
|
+
)
|
|
1169
|
+
return dynamic_compression_ratio
|
|
1170
|
+
|
|
1171
|
+
def token_segment(
|
|
1172
|
+
self,
|
|
1173
|
+
text_input_ids: List[int],
|
|
1174
|
+
iterative_size: int,
|
|
1175
|
+
segments: List[str],
|
|
1176
|
+
global_dynamic_rate: List[float],
|
|
1177
|
+
global_dynamic_compress: List[bool],
|
|
1178
|
+
):
|
|
1179
|
+
decode_window = 3
|
|
1180
|
+
seg_idx, seg_seen, token_seen_num, last_rate = 0, 0, 0, -1
|
|
1181
|
+
dynamic_compression_rate, local_compresssion_rate = [], []
|
|
1182
|
+
for i in range(len(text_input_ids)):
|
|
1183
|
+
if i < decode_window:
|
|
1184
|
+
id_pre, id_cur = text_input_ids[:i], text_input_ids[: i + 1]
|
|
1185
|
+
else:
|
|
1186
|
+
id_pre, id_cur = (
|
|
1187
|
+
text_input_ids[i - decode_window + 1 : i],
|
|
1188
|
+
text_input_ids[i - decode_window + 1 : i + 1],
|
|
1189
|
+
)
|
|
1190
|
+
cur_word = self.tokenizer.decode(id_cur)[len(self.tokenizer.decode(id_pre)) :]
|
|
1191
|
+
cur_word_len = len(cur_word)
|
|
1192
|
+
if cur_word_len and cur_word_len >= len(segments[seg_idx]) - seg_seen:
|
|
1193
|
+
possible_rate, possible_compress = [], []
|
|
1194
|
+
while cur_word_len and cur_word_len >= len(segments[seg_idx]) - seg_seen:
|
|
1195
|
+
possible_rate.append(global_dynamic_rate[seg_idx])
|
|
1196
|
+
possible_compress.append(global_dynamic_compress[seg_idx])
|
|
1197
|
+
cur_word_len -= len(segments[seg_idx]) - seg_seen
|
|
1198
|
+
seg_idx += 1
|
|
1199
|
+
seg_seen = 0
|
|
1200
|
+
if cur_word_len:
|
|
1201
|
+
possible_rate.append(global_dynamic_rate[seg_idx])
|
|
1202
|
+
possible_compress.append(global_dynamic_compress[seg_idx])
|
|
1203
|
+
new_rate = 1.0 if False in possible_compress else min(possible_rate)
|
|
1204
|
+
else:
|
|
1205
|
+
new_rate = global_dynamic_rate[seg_idx]
|
|
1206
|
+
if new_rate != last_rate and i - token_seen_num:
|
|
1207
|
+
local_compresssion_rate.append((i - token_seen_num, last_rate))
|
|
1208
|
+
token_seen_num = i
|
|
1209
|
+
last_rate = new_rate
|
|
1210
|
+
seg_seen += cur_word_len
|
|
1211
|
+
if (i + 1) % iterative_size == 0:
|
|
1212
|
+
if token_seen_num != i + 1:
|
|
1213
|
+
local_compresssion_rate.append((i + 1 - token_seen_num, last_rate))
|
|
1214
|
+
token_seen_num = i + 1
|
|
1215
|
+
dynamic_compression_rate.append(local_compresssion_rate[:])
|
|
1216
|
+
local_compresssion_rate = []
|
|
1217
|
+
if token_seen_num != len(text_input_ids):
|
|
1218
|
+
local_compresssion_rate.append((len(text_input_ids) - token_seen_num, last_rate))
|
|
1219
|
+
if local_compresssion_rate != []:
|
|
1220
|
+
dynamic_compression_rate.append(local_compresssion_rate[:])
|
|
1221
|
+
return dynamic_compression_rate
|
|
1222
|
+
|
|
1223
|
+
def control_context_budget(
|
|
1224
|
+
self,
|
|
1225
|
+
context: List[str],
|
|
1226
|
+
context_tokens_length: List[int],
|
|
1227
|
+
target_token: float,
|
|
1228
|
+
force_context_ids: List[int] = None,
|
|
1229
|
+
force_context_number: int = None,
|
|
1230
|
+
question: str = "",
|
|
1231
|
+
condition_in_question: str = "none",
|
|
1232
|
+
reorder_context: str = "original",
|
|
1233
|
+
dynamic_context_compression_ratio: float = 0.0,
|
|
1234
|
+
rank_method: str = "longllmlingua",
|
|
1235
|
+
context_budget: str = "+100",
|
|
1236
|
+
context_segs: List[List[str]] = None,
|
|
1237
|
+
context_segs_rate: List[List[float]] = None,
|
|
1238
|
+
context_segs_compress: List[List[bool]] = None,
|
|
1239
|
+
strict_preserve_uncompressed: bool = True,
|
|
1240
|
+
):
|
|
1241
|
+
demostrations_sort = self.get_rank_results(
|
|
1242
|
+
context,
|
|
1243
|
+
question,
|
|
1244
|
+
rank_method,
|
|
1245
|
+
condition_in_question,
|
|
1246
|
+
context_tokens_length,
|
|
1247
|
+
)
|
|
1248
|
+
|
|
1249
|
+
if target_token < 0:
|
|
1250
|
+
target_token = 100
|
|
1251
|
+
target_token = eval("target_token" + context_budget)
|
|
1252
|
+
res = []
|
|
1253
|
+
used = force_context_ids if force_context_ids is not None else []
|
|
1254
|
+
if context_segs is not None and strict_preserve_uncompressed:
|
|
1255
|
+
for idx, _ in enumerate(context):
|
|
1256
|
+
if False in context_segs_compress[idx] and idx not in used:
|
|
1257
|
+
used.append(idx)
|
|
1258
|
+
|
|
1259
|
+
self.context_idxs.append([x for idx, (x, _) in enumerate(demostrations_sort)])
|
|
1260
|
+
for idx, _ in demostrations_sort:
|
|
1261
|
+
if idx >= len(context_tokens_length):
|
|
1262
|
+
continue
|
|
1263
|
+
target_token -= context_tokens_length[idx]
|
|
1264
|
+
if idx not in used:
|
|
1265
|
+
used.append(idx)
|
|
1266
|
+
if target_token < 0 or (force_context_number is not None and len(res) >= force_context_number):
|
|
1267
|
+
break
|
|
1268
|
+
original_used = used
|
|
1269
|
+
if reorder_context == "original":
|
|
1270
|
+
used = sorted(used)
|
|
1271
|
+
elif reorder_context == "two_stage":
|
|
1272
|
+
l, r = [_ for idx, _ in enumerate(used) if idx % 2 == 0], [_ for idx, _ in enumerate(used) if idx % 2 == 1]
|
|
1273
|
+
used = l + r[::-1]
|
|
1274
|
+
|
|
1275
|
+
if dynamic_context_compression_ratio > 0:
|
|
1276
|
+
N = len(used)
|
|
1277
|
+
dynamic_ratio = [
|
|
1278
|
+
i * (abs(dynamic_context_compression_ratio) / (N - 1)) if N > 1 else 0 for i in range(-(N - 1), N, 2)
|
|
1279
|
+
][::-1]
|
|
1280
|
+
dynamic_ratio_map = {i: j for i, j in zip(original_used, dynamic_ratio)}
|
|
1281
|
+
dynamic_ratio = [dynamic_ratio_map[i] for i in used]
|
|
1282
|
+
else:
|
|
1283
|
+
dynamic_ratio = [0.0] * len(used)
|
|
1284
|
+
|
|
1285
|
+
res = [context[idx] for idx in used if idx < len(context)]
|
|
1286
|
+
return res, dynamic_ratio, used
|
|
1287
|
+
|
|
1288
|
+
def control_sentence_budget(
|
|
1289
|
+
self,
|
|
1290
|
+
context: List[str],
|
|
1291
|
+
target_token: float,
|
|
1292
|
+
keep_first_sentence: int = 0,
|
|
1293
|
+
keep_last_sentence: int = 0,
|
|
1294
|
+
keep_sentence_number: int = 0,
|
|
1295
|
+
high_priority_bonus: int = 100,
|
|
1296
|
+
token_budget_ratio: float = 1.4,
|
|
1297
|
+
question: str = "",
|
|
1298
|
+
condition_in_question: str = "none",
|
|
1299
|
+
rank_method: str = "longllmlingua",
|
|
1300
|
+
context_segs: List[List[str]] = None,
|
|
1301
|
+
context_segs_rate: List[List[float]] = None,
|
|
1302
|
+
context_segs_compress: List[List[bool]] = None,
|
|
1303
|
+
):
|
|
1304
|
+
def keep_sentence(dem_idx: int, sent_keep: int):
|
|
1305
|
+
idxs = sorted(dem_g[dem_idx], key=lambda x: sentence_ppl[x])[:sent_keep]
|
|
1306
|
+
for idx in idxs:
|
|
1307
|
+
sentence_ppl[idx] += high_priority_bonus
|
|
1308
|
+
|
|
1309
|
+
def sync_sentence(sentences, text):
|
|
1310
|
+
seen_text = 0
|
|
1311
|
+
sentence_num = len(sentences)
|
|
1312
|
+
new_sentences = []
|
|
1313
|
+
for i, s in enumerate(sentences):
|
|
1314
|
+
assert s == text[seen_text : seen_text + len(s)]
|
|
1315
|
+
if i == sentence_num - 1:
|
|
1316
|
+
new_sentences.append(text[seen_text:])
|
|
1317
|
+
break
|
|
1318
|
+
next_sentence_start = text.find(sentences[i + 1][:5], seen_text + len(s))
|
|
1319
|
+
new_sentences.append(text[seen_text:next_sentence_start])
|
|
1320
|
+
seen_text = next_sentence_start
|
|
1321
|
+
assert "".join(new_sentences) == text
|
|
1322
|
+
return new_sentences
|
|
1323
|
+
|
|
1324
|
+
sentences = [nltk.sent_tokenize(c) for c in context]
|
|
1325
|
+
sentences = [sync_sentence(s, c) for s, c in zip(sentences, context)]
|
|
1326
|
+
dem_g, s2de, idx = defaultdict(set), defaultdict(int), 0
|
|
1327
|
+
for idx_d, s in enumerate(sentences):
|
|
1328
|
+
for _ in s:
|
|
1329
|
+
dem_g[idx_d].add(idx)
|
|
1330
|
+
s2de[idx] = idx_d
|
|
1331
|
+
idx += 1
|
|
1332
|
+
|
|
1333
|
+
if context_segs is not None:
|
|
1334
|
+
sen2seg_ratio = {}
|
|
1335
|
+
idx = 0
|
|
1336
|
+
for idx_d, sentences_each_context in enumerate(sentences):
|
|
1337
|
+
segments_length = [len(s) for s in context_segs[idx_d]]
|
|
1338
|
+
seg_idx, cur_seg_seen = 0, 0
|
|
1339
|
+
for sentence in sentences_each_context:
|
|
1340
|
+
sentence_seg_ratio = []
|
|
1341
|
+
remain = len(sentence)
|
|
1342
|
+
while remain:
|
|
1343
|
+
if segments_length[seg_idx] - cur_seg_seen <= remain:
|
|
1344
|
+
new_seg_len = segments_length[seg_idx] - cur_seg_seen
|
|
1345
|
+
sentence_seg_ratio.append(
|
|
1346
|
+
(
|
|
1347
|
+
new_seg_len,
|
|
1348
|
+
context_segs_rate[idx_d][seg_idx],
|
|
1349
|
+
context_segs_compress[idx_d][seg_idx],
|
|
1350
|
+
)
|
|
1351
|
+
)
|
|
1352
|
+
seg_idx += 1
|
|
1353
|
+
cur_seg_seen = 0
|
|
1354
|
+
remain -= new_seg_len
|
|
1355
|
+
else:
|
|
1356
|
+
sentence_seg_ratio.append(
|
|
1357
|
+
(
|
|
1358
|
+
remain,
|
|
1359
|
+
context_segs_rate[idx_d][seg_idx],
|
|
1360
|
+
context_segs_compress[idx_d][seg_idx],
|
|
1361
|
+
)
|
|
1362
|
+
)
|
|
1363
|
+
cur_seg_seen += remain
|
|
1364
|
+
remain = 0
|
|
1365
|
+
sen2seg_ratio[idx] = sentence_seg_ratio
|
|
1366
|
+
idx += 1
|
|
1367
|
+
|
|
1368
|
+
context_sentences = [s for ii in sentences for s in ii]
|
|
1369
|
+
sentence_tokens_length = [self.get_token_length(sentence) for sentence in context_sentences]
|
|
1370
|
+
N = len(context_sentences)
|
|
1371
|
+
flags = list(range(len(context_sentences)))
|
|
1372
|
+
if len(sentence_tokens_length) == 1:
|
|
1373
|
+
segments_info = []
|
|
1374
|
+
if context_segs is not None:
|
|
1375
|
+
segments_info.append(sen2seg_ratio[0])
|
|
1376
|
+
return context, segments_info
|
|
1377
|
+
if rank_method == "longllmlingua":
|
|
1378
|
+
sentence_ppl = [
|
|
1379
|
+
self.get_condition_ppl(sentence, question, condition_in_question).cpu().numpy().item()
|
|
1380
|
+
for sentence in context_sentences
|
|
1381
|
+
]
|
|
1382
|
+
if keep_first_sentence:
|
|
1383
|
+
sentence_ppl[:keep_first_sentence] = [
|
|
1384
|
+
ii + high_priority_bonus for ii in sentence_ppl[:keep_first_sentence]
|
|
1385
|
+
]
|
|
1386
|
+
if keep_last_sentence:
|
|
1387
|
+
sentence_ppl[-keep_last_sentence:] = [
|
|
1388
|
+
ii + high_priority_bonus for ii in sentence_ppl[-keep_last_sentence:]
|
|
1389
|
+
]
|
|
1390
|
+
if keep_sentence_number:
|
|
1391
|
+
for dem_idx in range(len(sentences)):
|
|
1392
|
+
keep_sentence(dem_idx, keep_sentence_number)
|
|
1393
|
+
sort_direct = -1 if condition_in_question == "none" else 1
|
|
1394
|
+
sent_sort = sorted(enumerate(sentence_ppl), key=lambda x: sort_direct * x[1])
|
|
1395
|
+
else:
|
|
1396
|
+
sent_sort = self.get_rank_results(
|
|
1397
|
+
context_sentences,
|
|
1398
|
+
question,
|
|
1399
|
+
rank_method,
|
|
1400
|
+
condition_in_question,
|
|
1401
|
+
[0] * len(context_sentences),
|
|
1402
|
+
)
|
|
1403
|
+
|
|
1404
|
+
sentence_flags = [False] * N
|
|
1405
|
+
if target_token < 0:
|
|
1406
|
+
target_token = 100
|
|
1407
|
+
target_token *= token_budget_ratio
|
|
1408
|
+
res = []
|
|
1409
|
+
for idx, _ in sent_sort:
|
|
1410
|
+
idx = flags[idx]
|
|
1411
|
+
target_token -= sentence_tokens_length[idx]
|
|
1412
|
+
sentence_flags[idx] = True
|
|
1413
|
+
if target_token < 0:
|
|
1414
|
+
break
|
|
1415
|
+
|
|
1416
|
+
if context_segs is not None:
|
|
1417
|
+
for idx in range(N):
|
|
1418
|
+
preserved = [sen_seg_info[2] for sen_seg_info in sen2seg_ratio[idx]]
|
|
1419
|
+
if False in preserved:
|
|
1420
|
+
sentence_flags[idx] = True
|
|
1421
|
+
|
|
1422
|
+
idx = 0
|
|
1423
|
+
res = []
|
|
1424
|
+
new_segments_info = []
|
|
1425
|
+
for s in sentences:
|
|
1426
|
+
tmp = [jj for ii, jj in enumerate(s) if sentence_flags[idx + ii]]
|
|
1427
|
+
res.append("".join(tmp))
|
|
1428
|
+
if context_segs is not None:
|
|
1429
|
+
segment_ratio = []
|
|
1430
|
+
for ii in range(len(s)):
|
|
1431
|
+
if sentence_flags[idx + ii]:
|
|
1432
|
+
segment_ratio.extend(sen2seg_ratio[idx + ii])
|
|
1433
|
+
new_segments_info.append(segment_ratio)
|
|
1434
|
+
idx += len(s)
|
|
1435
|
+
return res, new_segments_info
|
|
1436
|
+
|
|
1437
|
+
def get_compressed_input(
|
|
1438
|
+
self,
|
|
1439
|
+
loss,
|
|
1440
|
+
input_ids,
|
|
1441
|
+
attention_mask,
|
|
1442
|
+
end=200,
|
|
1443
|
+
iterative_size=200,
|
|
1444
|
+
threshold=0.5,
|
|
1445
|
+
keep_flag=None,
|
|
1446
|
+
split_token_id: int = 13,
|
|
1447
|
+
start: int = 0,
|
|
1448
|
+
self_loss=None,
|
|
1449
|
+
self_input_ids=None,
|
|
1450
|
+
self_attention_mask=None,
|
|
1451
|
+
):
|
|
1452
|
+
if self_loss is not None:
|
|
1453
|
+
need_idx = torch.concat(
|
|
1454
|
+
[
|
|
1455
|
+
loss[:start] > 0,
|
|
1456
|
+
self_loss[: loss[start:].shape[0]] - loss[start:] > threshold,
|
|
1457
|
+
loss[:1] > 0,
|
|
1458
|
+
]
|
|
1459
|
+
)
|
|
1460
|
+
else:
|
|
1461
|
+
need_idx = torch.concat([loss > threshold, loss[:1] > 0])
|
|
1462
|
+
need_idx[end:] = 1
|
|
1463
|
+
need_idx[: end - iterative_size] = 1
|
|
1464
|
+
loss = loss[need_idx[:-1]]
|
|
1465
|
+
if self_loss is not None:
|
|
1466
|
+
if need_idx.shape[0] < self_loss.shape[0] + start + 1:
|
|
1467
|
+
need_idx = torch.cat(
|
|
1468
|
+
[
|
|
1469
|
+
need_idx,
|
|
1470
|
+
torch.ones(
|
|
1471
|
+
self_loss.shape[0] - need_idx.shape[0] + start + 1,
|
|
1472
|
+
dtype=torch.bool,
|
|
1473
|
+
).to(need_idx.device),
|
|
1474
|
+
]
|
|
1475
|
+
)
|
|
1476
|
+
self_loss = self_loss[need_idx[start:-1]]
|
|
1477
|
+
|
|
1478
|
+
if need_idx.shape[0] < input_ids.shape[1]:
|
|
1479
|
+
need_idx = torch.cat(
|
|
1480
|
+
[
|
|
1481
|
+
need_idx,
|
|
1482
|
+
torch.ones(input_ids.shape[1] - need_idx.shape[0], dtype=torch.bool).to(need_idx.device),
|
|
1483
|
+
]
|
|
1484
|
+
)
|
|
1485
|
+
elif need_idx.shape[0] > input_ids.shape[1]:
|
|
1486
|
+
need_idx = need_idx[: input_ids.shape[1]]
|
|
1487
|
+
|
|
1488
|
+
if keep_flag is not None:
|
|
1489
|
+
need_idx[keep_flag == 1] = 1
|
|
1490
|
+
last = -1
|
|
1491
|
+
if keep_flag is not None:
|
|
1492
|
+
for ii in range(max(0, end - iterative_size), end):
|
|
1493
|
+
if need_idx[ii] != 1:
|
|
1494
|
+
continue
|
|
1495
|
+
now = input_ids[0][ii].detach().cpu().item()
|
|
1496
|
+
if now == split_token_id and last == split_token_id and keep_flag[ii].detach().cpu().item() == 0:
|
|
1497
|
+
need_idx[ii] = 0
|
|
1498
|
+
else:
|
|
1499
|
+
last = now
|
|
1500
|
+
compressed_input_ids = input_ids[attention_mask == 1][need_idx].unsqueeze(0)
|
|
1501
|
+
compressed_attention_mask = attention_mask[attention_mask == 1][need_idx].unsqueeze(0)
|
|
1502
|
+
|
|
1503
|
+
if self_loss is not None:
|
|
1504
|
+
self_compressed_input_ids = self_input_ids[self_attention_mask == 1][need_idx[start:]].unsqueeze(0)
|
|
1505
|
+
self_compressed_attention_mask = self_attention_mask[self_attention_mask == 1][need_idx[start:]].unsqueeze(
|
|
1506
|
+
0
|
|
1507
|
+
)
|
|
1508
|
+
else:
|
|
1509
|
+
self_compressed_input_ids, self_compressed_attention_mask = None, None
|
|
1510
|
+
if keep_flag is not None:
|
|
1511
|
+
if len(keep_flag) > len(need_idx):
|
|
1512
|
+
keep_flag = torch.cat(
|
|
1513
|
+
[
|
|
1514
|
+
keep_flag[:start],
|
|
1515
|
+
keep_flag[start : len(need_idx) + start][need_idx],
|
|
1516
|
+
keep_flag[start + len(need_idx) :],
|
|
1517
|
+
]
|
|
1518
|
+
)
|
|
1519
|
+
else:
|
|
1520
|
+
keep_flag = keep_flag[need_idx]
|
|
1521
|
+
end -= (need_idx[:end] == 0).sum()
|
|
1522
|
+
return (
|
|
1523
|
+
compressed_input_ids,
|
|
1524
|
+
compressed_attention_mask,
|
|
1525
|
+
keep_flag,
|
|
1526
|
+
end,
|
|
1527
|
+
loss,
|
|
1528
|
+
self_loss,
|
|
1529
|
+
self_compressed_input_ids,
|
|
1530
|
+
self_compressed_attention_mask,
|
|
1531
|
+
)
|
|
1532
|
+
|
|
1533
|
+
def get_estimate_threshold_base_distribution(self, ppl, ratio: float, condition_flag: bool = False):
|
|
1534
|
+
if ratio == 1.0:
|
|
1535
|
+
return float("-inf")
|
|
1536
|
+
ppl = ppl[ppl != 10000]
|
|
1537
|
+
target_token = max(0, min(len(ppl) - 1, int(len(ppl) * ratio) - 1))
|
|
1538
|
+
return ppl.sort(descending=not condition_flag).values[target_token].detach().cpu().item()
|
|
1539
|
+
|
|
1540
|
+
def iterative_compress_prompt(
|
|
1541
|
+
self,
|
|
1542
|
+
context: List[str],
|
|
1543
|
+
target_token: float,
|
|
1544
|
+
iterative_size: int = 200,
|
|
1545
|
+
keep_split: bool = False,
|
|
1546
|
+
split_token_id: int = 13,
|
|
1547
|
+
start: int = 0,
|
|
1548
|
+
dynamic_ratio: list = None,
|
|
1549
|
+
condition_compare: bool = False,
|
|
1550
|
+
segments_info: List[List[tuple]] = None,
|
|
1551
|
+
):
|
|
1552
|
+
if segments_info is None or segments_info == []:
|
|
1553
|
+
iterative_ratios = self.get_dynamic_compression_ratio(
|
|
1554
|
+
context, target_token, iterative_size, dynamic_ratio, start
|
|
1555
|
+
)
|
|
1556
|
+
else:
|
|
1557
|
+
iterative_ratios = self.get_structured_dynamic_compression_ratio(
|
|
1558
|
+
context, iterative_size, dynamic_ratio, start, segments_info
|
|
1559
|
+
)
|
|
1560
|
+
context = "\n\n".join(context)
|
|
1561
|
+
tokenized_text = self.tokenizer(context, return_tensors="pt", add_special_tokens=False)
|
|
1562
|
+
input_ids = tokenized_text["input_ids"].to(self.device)
|
|
1563
|
+
attention_mask = tokenized_text["attention_mask"].to(self.device)
|
|
1564
|
+
|
|
1565
|
+
N = (attention_mask == 1).sum()
|
|
1566
|
+
compressed_input_ids, compressed_attention_mask = input_ids, attention_mask
|
|
1567
|
+
if condition_compare:
|
|
1568
|
+
self_input_ids, self_attention_mask = (
|
|
1569
|
+
input_ids[:, start:],
|
|
1570
|
+
attention_mask[:, start:],
|
|
1571
|
+
)
|
|
1572
|
+
self_compressed_input_ids, self_compressed_attention_mask = (
|
|
1573
|
+
self_input_ids,
|
|
1574
|
+
self_attention_mask,
|
|
1575
|
+
)
|
|
1576
|
+
|
|
1577
|
+
end = min(iterative_size + start, compressed_input_ids.shape[1])
|
|
1578
|
+
threshold, keep_flag = None, None
|
|
1579
|
+
if keep_split:
|
|
1580
|
+
input_ids_numpy = input_ids.cpu().detach().numpy()[0]
|
|
1581
|
+
N = len(input_ids_numpy)
|
|
1582
|
+
keep_flag = [
|
|
1583
|
+
int(
|
|
1584
|
+
(ii > 0 and input_ids_numpy[ii] == split_token_id and input_ids_numpy[ii - 1] == split_token_id)
|
|
1585
|
+
or (
|
|
1586
|
+
ii < N - 1
|
|
1587
|
+
and input_ids_numpy[ii] == split_token_id
|
|
1588
|
+
and input_ids_numpy[ii + 1] == split_token_id
|
|
1589
|
+
)
|
|
1590
|
+
)
|
|
1591
|
+
for ii in range(N)
|
|
1592
|
+
]
|
|
1593
|
+
keep_flag = torch.tensor(keep_flag).to(self.device)
|
|
1594
|
+
past_key_values, past_loss, ready_end = None, None, 0
|
|
1595
|
+
self_past_key_values, self_past_loss, self_ready_end = None, None, 0
|
|
1596
|
+
pop_compressed_input_ids, pop_self_compressed_input_ids = None, None
|
|
1597
|
+
idx = 0
|
|
1598
|
+
while end <= compressed_input_ids.shape[1]:
|
|
1599
|
+
if end > self.max_position_embeddings and past_key_values is not None:
|
|
1600
|
+
# KV-Cache Compression
|
|
1601
|
+
e, s = end - self.max_position_embeddings, min(self.cache_bos_num + start, self.max_position_embeddings)
|
|
1602
|
+
if pop_compressed_input_ids is None:
|
|
1603
|
+
pop_compressed_input_ids = compressed_input_ids[:, :e]
|
|
1604
|
+
else:
|
|
1605
|
+
pop_compressed_input_ids = torch.cat(
|
|
1606
|
+
[pop_compressed_input_ids, compressed_input_ids[:, :e]], dim=-1
|
|
1607
|
+
)
|
|
1608
|
+
compressed_input_ids = compressed_input_ids[:, e:]
|
|
1609
|
+
compressed_attention_mask = compressed_attention_mask[:, e:]
|
|
1610
|
+
past_key_values = [
|
|
1611
|
+
[
|
|
1612
|
+
torch.cat([k[..., :s, :], k[..., s + e :, :]], dim=-2),
|
|
1613
|
+
torch.cat([v[..., :s, :], v[..., s + e :, :]], dim=-2),
|
|
1614
|
+
]
|
|
1615
|
+
for k, v in past_key_values
|
|
1616
|
+
]
|
|
1617
|
+
if keep_flag is not None:
|
|
1618
|
+
keep_flag = keep_flag[e:]
|
|
1619
|
+
end, ready_end = end - e, ready_end - e
|
|
1620
|
+
if condition_compare:
|
|
1621
|
+
s = min(s, self_past_key_values[0][0].shape[2] - e)
|
|
1622
|
+
self_ready_end -= e
|
|
1623
|
+
if pop_self_compressed_input_ids is None:
|
|
1624
|
+
pop_self_compressed_input_ids = self_compressed_input_ids[:, :e]
|
|
1625
|
+
else:
|
|
1626
|
+
pop_self_compressed_input_ids = torch.cat(
|
|
1627
|
+
[
|
|
1628
|
+
pop_self_compressed_input_ids,
|
|
1629
|
+
self_compressed_input_ids[:, :e],
|
|
1630
|
+
],
|
|
1631
|
+
dim=-1,
|
|
1632
|
+
)
|
|
1633
|
+
self_compressed_input_ids = self_compressed_input_ids[:, e:]
|
|
1634
|
+
self_compressed_attention_mask = self_compressed_attention_mask[:, e:]
|
|
1635
|
+
self_past_key_values = [
|
|
1636
|
+
[
|
|
1637
|
+
torch.cat([k[..., :s, :], k[..., s + e :, :]], dim=-2),
|
|
1638
|
+
torch.cat([v[..., :s, :], v[..., s + e :, :]], dim=-2),
|
|
1639
|
+
]
|
|
1640
|
+
for k, v in self_past_key_values
|
|
1641
|
+
]
|
|
1642
|
+
|
|
1643
|
+
loss, past_key_values = self.get_ppl(
|
|
1644
|
+
"",
|
|
1645
|
+
"token",
|
|
1646
|
+
compressed_input_ids,
|
|
1647
|
+
compressed_attention_mask,
|
|
1648
|
+
past_key_values=past_key_values,
|
|
1649
|
+
return_kv=True,
|
|
1650
|
+
end=end if idx else None,
|
|
1651
|
+
)
|
|
1652
|
+
if loss.shape[0] == 0:
|
|
1653
|
+
break
|
|
1654
|
+
if past_loss is not None:
|
|
1655
|
+
if end - 1 > len(past_loss):
|
|
1656
|
+
past_loss = torch.cat([past_loss, torch.zeros_like(loss)[: end - 1 - len(past_loss)]])
|
|
1657
|
+
past_loss[ready_end : end - 1] = loss
|
|
1658
|
+
loss = past_loss
|
|
1659
|
+
else:
|
|
1660
|
+
past_loss = loss
|
|
1661
|
+
if idx:
|
|
1662
|
+
past_key_values = [
|
|
1663
|
+
[k[:, :, : end - iterative_size], v[:, :, : end - iterative_size]] for k, v in past_key_values
|
|
1664
|
+
]
|
|
1665
|
+
else:
|
|
1666
|
+
past_key_values = None
|
|
1667
|
+
|
|
1668
|
+
if condition_compare:
|
|
1669
|
+
self_loss, self_past_key_values = self.get_ppl(
|
|
1670
|
+
"",
|
|
1671
|
+
"token",
|
|
1672
|
+
self_compressed_input_ids,
|
|
1673
|
+
self_compressed_attention_mask,
|
|
1674
|
+
past_key_values=self_past_key_values,
|
|
1675
|
+
return_kv=True,
|
|
1676
|
+
end=end - start if idx else None,
|
|
1677
|
+
)
|
|
1678
|
+
if self_past_loss is not None:
|
|
1679
|
+
if end - start - 1 > len(self_past_loss):
|
|
1680
|
+
self_past_loss = torch.cat(
|
|
1681
|
+
[
|
|
1682
|
+
self_past_loss,
|
|
1683
|
+
torch.zeros_like(self_loss)[: end - 1 - start - len(self_past_loss)],
|
|
1684
|
+
]
|
|
1685
|
+
)
|
|
1686
|
+
self_past_loss[self_ready_end : end - start - 1] = self_loss
|
|
1687
|
+
self_loss = self_past_loss
|
|
1688
|
+
else:
|
|
1689
|
+
self_past_loss = self_loss
|
|
1690
|
+
if idx:
|
|
1691
|
+
self_past_key_values = [
|
|
1692
|
+
[
|
|
1693
|
+
k[:, :, : end - iterative_size - start],
|
|
1694
|
+
v[:, :, : end - iterative_size - start],
|
|
1695
|
+
]
|
|
1696
|
+
for k, v in self_past_key_values
|
|
1697
|
+
]
|
|
1698
|
+
else:
|
|
1699
|
+
self_past_key_values = None
|
|
1700
|
+
|
|
1701
|
+
self_ready_end = end - start - iterative_size if not (start and idx == 0) else 0
|
|
1702
|
+
ready_end = end - iterative_size if not (start and idx == 0) else 0
|
|
1703
|
+
|
|
1704
|
+
for delta_end, ratio in iterative_ratios[idx]:
|
|
1705
|
+
loss = past_loss
|
|
1706
|
+
if condition_compare:
|
|
1707
|
+
self_loss = self_past_loss
|
|
1708
|
+
threshold = self.get_estimate_threshold_base_distribution(
|
|
1709
|
+
self_loss[: loss[start:].shape[0]] - loss[start:], ratio, False
|
|
1710
|
+
)
|
|
1711
|
+
else:
|
|
1712
|
+
threshold = self.get_estimate_threshold_base_distribution(loss, ratio, False)
|
|
1713
|
+
|
|
1714
|
+
(
|
|
1715
|
+
compressed_input_ids,
|
|
1716
|
+
compressed_attention_mask,
|
|
1717
|
+
keep_flag,
|
|
1718
|
+
end,
|
|
1719
|
+
past_loss,
|
|
1720
|
+
self_past_loss,
|
|
1721
|
+
self_compressed_input_ids,
|
|
1722
|
+
self_compressed_attention_mask,
|
|
1723
|
+
) = self.get_compressed_input(
|
|
1724
|
+
loss,
|
|
1725
|
+
compressed_input_ids,
|
|
1726
|
+
compressed_attention_mask,
|
|
1727
|
+
end - iterative_size + delta_end,
|
|
1728
|
+
iterative_size=delta_end,
|
|
1729
|
+
threshold=threshold,
|
|
1730
|
+
keep_flag=keep_flag,
|
|
1731
|
+
split_token_id=split_token_id,
|
|
1732
|
+
start=start,
|
|
1733
|
+
self_loss=self_loss if condition_compare else None,
|
|
1734
|
+
self_input_ids=(self_compressed_input_ids if condition_compare else None),
|
|
1735
|
+
self_attention_mask=(self_compressed_attention_mask if condition_compare else None),
|
|
1736
|
+
)
|
|
1737
|
+
end += iterative_size
|
|
1738
|
+
idx += 1
|
|
1739
|
+
if pop_compressed_input_ids is not None:
|
|
1740
|
+
compressed_input_ids = torch.cat([pop_compressed_input_ids, compressed_input_ids], dim=-1)
|
|
1741
|
+
return compressed_input_ids[:, start:], compressed_attention_mask[:, start:]
|
|
1742
|
+
|
|
1743
|
+
def recover(
|
|
1744
|
+
self,
|
|
1745
|
+
original_prompt: str,
|
|
1746
|
+
compressed_prompt: str,
|
|
1747
|
+
response: str,
|
|
1748
|
+
):
|
|
1749
|
+
def match_from_compressed(response_word):
|
|
1750
|
+
response_input_ids = self.tokenizer(response_word, add_special_tokens=False)["input_ids"]
|
|
1751
|
+
response_set, response_c = set(response_input_ids), defaultdict(list)
|
|
1752
|
+
for idx in range(M):
|
|
1753
|
+
if original_input_ids[idx] in response_set:
|
|
1754
|
+
response_c[original_input_ids[idx]].append(idx)
|
|
1755
|
+
res, res_min, res_c = None, float("inf"), 1
|
|
1756
|
+
n = len(response_input_ids)
|
|
1757
|
+
for l in response_c[response_input_ids[0]]:
|
|
1758
|
+
x, y, c = 0, l, 1
|
|
1759
|
+
for x in range(1, n):
|
|
1760
|
+
idx = bisect.bisect_right(response_c[response_input_ids[x]], y)
|
|
1761
|
+
if idx >= len(response_c[response_input_ids[x]]) or response_c[response_input_ids[x]][idx] - y > 10:
|
|
1762
|
+
continue
|
|
1763
|
+
c += 1
|
|
1764
|
+
y = response_c[response_input_ids[x]][idx]
|
|
1765
|
+
if c > res_c:
|
|
1766
|
+
res_c = c
|
|
1767
|
+
res_min = y - l + 1
|
|
1768
|
+
res = (l, y + 1)
|
|
1769
|
+
elif c == res_c and y - l + 1 < res_min:
|
|
1770
|
+
res_min = y - l + 1
|
|
1771
|
+
res = (l, y + 1)
|
|
1772
|
+
|
|
1773
|
+
if res is None:
|
|
1774
|
+
return response_word
|
|
1775
|
+
# while l > 0 and not self.tokenizer.convert_ids_to_tokens(original_input_ids[l]).startswith("_"):
|
|
1776
|
+
# l -= 1
|
|
1777
|
+
# while r < M - 1 and not self.tokenizer.convert_ids_to_tokens(original_input_ids[l]).startswith("_"):
|
|
1778
|
+
# l -= 1
|
|
1779
|
+
return self.tokenizer.decode(original_input_ids[res[0] : res[1]])
|
|
1780
|
+
|
|
1781
|
+
response_words = response.split(" ")
|
|
1782
|
+
|
|
1783
|
+
original_input_ids = self.tokenizer(original_prompt, add_special_tokens=False)["input_ids"]
|
|
1784
|
+
N, M = len(response_words), len(original_input_ids)
|
|
1785
|
+
recovered_response_words = []
|
|
1786
|
+
l = 0
|
|
1787
|
+
while l < N:
|
|
1788
|
+
if response_words[l] not in compressed_prompt:
|
|
1789
|
+
recovered_response_words.append(response_words[l])
|
|
1790
|
+
l += 1
|
|
1791
|
+
continue
|
|
1792
|
+
r = l
|
|
1793
|
+
while r + 1 < N and " ".join(response_words[l : r + 2]) in compressed_prompt:
|
|
1794
|
+
r += 1
|
|
1795
|
+
|
|
1796
|
+
match_words = match_from_compressed(" ".join(response_words[l : r + 1]))
|
|
1797
|
+
recovered_response_words.append(match_words)
|
|
1798
|
+
l = r + 1
|
|
1799
|
+
return " ".join(recovered_response_words)
|
|
1800
|
+
|
|
1801
|
+
def get_rank_results(
|
|
1802
|
+
self,
|
|
1803
|
+
context: list,
|
|
1804
|
+
question: str,
|
|
1805
|
+
rank_method: str,
|
|
1806
|
+
condition_in_question: str,
|
|
1807
|
+
context_tokens_length: list,
|
|
1808
|
+
):
|
|
1809
|
+
def get_distance_bm25(corpus, query):
|
|
1810
|
+
from rank_bm25 import BM25Okapi
|
|
1811
|
+
|
|
1812
|
+
tokenized_corpus = [doc.split(" ") for doc in corpus]
|
|
1813
|
+
bm25 = BM25Okapi(tokenized_corpus)
|
|
1814
|
+
tokenized_query = query.split(" ")
|
|
1815
|
+
doc_scores = bm25.get_scores(tokenized_query)
|
|
1816
|
+
idx = [(ii, 0) for ii in (-doc_scores).argsort()]
|
|
1817
|
+
return idx
|
|
1818
|
+
|
|
1819
|
+
def get_distance_gzip(corpus, query):
|
|
1820
|
+
def get_score(x, y):
|
|
1821
|
+
cx, cy = len(gzip.compress(x.encode())), len(gzip.compress(y.encode()))
|
|
1822
|
+
cxy = len(gzip.compress(f"{x} {y}".encode()))
|
|
1823
|
+
return (cxy - min(cx, cy)) / max(cx, cy)
|
|
1824
|
+
|
|
1825
|
+
import gzip
|
|
1826
|
+
|
|
1827
|
+
doc_scores = [get_score(doc, query) for doc in corpus]
|
|
1828
|
+
idx = [(ii, 0) for ii in np.argsort(doc_scores)]
|
|
1829
|
+
return idx
|
|
1830
|
+
|
|
1831
|
+
def get_distance_sentbert(corpus, query):
|
|
1832
|
+
from sentence_transformers import SentenceTransformer, util
|
|
1833
|
+
|
|
1834
|
+
if self.retrieval_model is None or self.retrieval_model_name != rank_method:
|
|
1835
|
+
self.retrieval_model = SentenceTransformer("multi-qa-mpnet-base-dot-v1")
|
|
1836
|
+
self.retrieval_model_name = rank_method
|
|
1837
|
+
doc_embeds = self.retrieval_model.encode(corpus)
|
|
1838
|
+
query = self.retrieval_model.encode(query)
|
|
1839
|
+
doc_scores = -util.dot_score(doc_embeds, query).cpu().numpy().reshape(-1)
|
|
1840
|
+
idx = [(ii, 0) for ii in np.argsort(doc_scores)]
|
|
1841
|
+
return idx
|
|
1842
|
+
|
|
1843
|
+
def get_distance_openai(corpus, query):
|
|
1844
|
+
import openai
|
|
1845
|
+
from sentence_transformers import util
|
|
1846
|
+
|
|
1847
|
+
openai.api_key = self.open_api_config.get("api_key", "")
|
|
1848
|
+
openai.api_base = self.open_api_config.get("api_base", "https://api.openai.com/v1")
|
|
1849
|
+
openai.api_type = self.open_api_config.get("api_type", "open_ai")
|
|
1850
|
+
openai.api_version = self.open_api_config.get("api_version", "2023-05-15")
|
|
1851
|
+
engine = self.open_api_config.get("engine", "text-embedding-ada-002")
|
|
1852
|
+
|
|
1853
|
+
def get_embed(text):
|
|
1854
|
+
return openai.Embedding.create(input=[text.replace("\n", " ")], engine=engine)["data"][0]["embedding"]
|
|
1855
|
+
|
|
1856
|
+
doc_embeds = [get_embed(i) for i in corpus]
|
|
1857
|
+
query = get_embed(query)
|
|
1858
|
+
doc_scores = -util.dot_score(doc_embeds, query).cpu().numpy().reshape(-1)
|
|
1859
|
+
idx = [(ii, 0) for ii in np.argsort(doc_scores)]
|
|
1860
|
+
return idx
|
|
1861
|
+
|
|
1862
|
+
def get_distance_sentbert_bge(corpus, query):
|
|
1863
|
+
from sentence_transformers import SentenceTransformer, util
|
|
1864
|
+
|
|
1865
|
+
if self.retrieval_model is None or self.retrieval_model_name != rank_method:
|
|
1866
|
+
self.retrieval_model = SentenceTransformer("BAAI/bge-large-en-v1.5")
|
|
1867
|
+
self.retrieval_model_name = rank_method
|
|
1868
|
+
doc_embeds = self.retrieval_model.encode([i for i in corpus], normalize_embeddings=True)
|
|
1869
|
+
query = self.retrieval_model.encode(query, normalize_embeddings=True)
|
|
1870
|
+
doc_scores = -util.dot_score(doc_embeds, query).cpu().numpy().reshape(-1)
|
|
1871
|
+
idx = [(ii, 0) for ii in np.argsort(doc_scores)]
|
|
1872
|
+
return idx
|
|
1873
|
+
|
|
1874
|
+
def get_distance_bge_ranker(corpus, query):
|
|
1875
|
+
from transformers import AutoModelForSequenceClassification, AutoTokenizer
|
|
1876
|
+
|
|
1877
|
+
pairs = [[i, query] for i in corpus]
|
|
1878
|
+
if self.retrieval_model is None or self.retrieval_model_name != rank_method:
|
|
1879
|
+
tokenizer = AutoTokenizer.from_pretrained("BAAI/bge-reranker-large")
|
|
1880
|
+
model = (
|
|
1881
|
+
AutoModelForSequenceClassification.from_pretrained("BAAI/bge-reranker-large").eval().to(self.device)
|
|
1882
|
+
)
|
|
1883
|
+
self.retrieval_model = [tokenizer, model]
|
|
1884
|
+
self.retrieval_model_name = rank_method
|
|
1885
|
+
with torch.inference_mode(mode=True):
|
|
1886
|
+
inputs = self.retrieval_model[0](
|
|
1887
|
+
pairs,
|
|
1888
|
+
padding=True,
|
|
1889
|
+
truncation=True,
|
|
1890
|
+
return_tensors="pt",
|
|
1891
|
+
max_length=512,
|
|
1892
|
+
).to(self.device)
|
|
1893
|
+
scores = (
|
|
1894
|
+
self.retrieval_model[1](**inputs, return_dict=True)
|
|
1895
|
+
.logits.view(
|
|
1896
|
+
-1,
|
|
1897
|
+
)
|
|
1898
|
+
.float()
|
|
1899
|
+
)
|
|
1900
|
+
idx = [(ii, 0) for ii in np.argsort(-scores.cpu())]
|
|
1901
|
+
return idx
|
|
1902
|
+
|
|
1903
|
+
def get_distance_bge_llmembedder(corpus, query):
|
|
1904
|
+
from transformers import AutoModel, AutoTokenizer
|
|
1905
|
+
|
|
1906
|
+
if self.retrieval_model is None or self.retrieval_model_name != rank_method:
|
|
1907
|
+
tokenizer = AutoTokenizer.from_pretrained("BAAI/llm-embedder")
|
|
1908
|
+
model = AutoModel.from_pretrained("BAAI/llm-embedder").eval().to(self.device)
|
|
1909
|
+
self.retrieval_model = [tokenizer, model]
|
|
1910
|
+
self.retrieval_model_name = rank_method
|
|
1911
|
+
|
|
1912
|
+
instruction_qa_query = "Represent this query for retrieving relevant documents: "
|
|
1913
|
+
instruction_qa_key = "Represent this document for retrieval: "
|
|
1914
|
+
queries = [instruction_qa_query + query for _ in corpus]
|
|
1915
|
+
keys = [instruction_qa_key + key for key in corpus]
|
|
1916
|
+
with torch.inference_mode(mode=True):
|
|
1917
|
+
query_inputs = self.retrieval_model[0](
|
|
1918
|
+
queries,
|
|
1919
|
+
padding=True,
|
|
1920
|
+
truncation=True,
|
|
1921
|
+
return_tensors="pt",
|
|
1922
|
+
max_length=512,
|
|
1923
|
+
).to(self.device)
|
|
1924
|
+
key_inputs = self.retrieval_model[0](
|
|
1925
|
+
keys,
|
|
1926
|
+
padding=True,
|
|
1927
|
+
truncation=True,
|
|
1928
|
+
return_tensors="pt",
|
|
1929
|
+
max_length=512,
|
|
1930
|
+
).to(self.device)
|
|
1931
|
+
query_outputs = self.retrieval_model[1](**query_inputs)
|
|
1932
|
+
key_outputs = self.retrieval_model[1](**key_inputs)
|
|
1933
|
+
# CLS pooling
|
|
1934
|
+
query_embeddings = query_outputs.last_hidden_state[:, 0]
|
|
1935
|
+
key_embeddings = key_outputs.last_hidden_state[:, 0]
|
|
1936
|
+
# Normalize
|
|
1937
|
+
query_embeddings = torch.nn.functional.normalize(query_embeddings, p=2, dim=1)
|
|
1938
|
+
key_embeddings = torch.nn.functional.normalize(key_embeddings, p=2, dim=1)
|
|
1939
|
+
similarity = query_embeddings @ key_embeddings.T
|
|
1940
|
+
idx = [(ii, 0) for ii in np.argsort(-similarity[0].cpu())]
|
|
1941
|
+
return idx
|
|
1942
|
+
|
|
1943
|
+
def get_distance_jinza(corpus, query):
|
|
1944
|
+
from numpy.linalg import norm
|
|
1945
|
+
from transformers import AutoModel
|
|
1946
|
+
|
|
1947
|
+
def cos_sim(a, b):
|
|
1948
|
+
return (a @ b.T) / (norm(a) * norm(b))
|
|
1949
|
+
|
|
1950
|
+
if self.retrieval_model is None or self.retrieval_model_name != rank_method:
|
|
1951
|
+
model = (
|
|
1952
|
+
AutoModel.from_pretrained("jinaai/jina-embeddings-v2-base-en", trust_remote_code=True)
|
|
1953
|
+
.eval()
|
|
1954
|
+
.to(self.device)
|
|
1955
|
+
)
|
|
1956
|
+
self.retrieval_model = model
|
|
1957
|
+
self.retrieval_model_name = rank_method
|
|
1958
|
+
|
|
1959
|
+
doc_embeds = self.retrieval_model.encode(corpus)
|
|
1960
|
+
query = self.retrieval_model.encode(query)
|
|
1961
|
+
doc_scores = cos_sim(doc_embeds, query)
|
|
1962
|
+
idx = [(ii, 0) for ii in np.argsort(-doc_scores)]
|
|
1963
|
+
return idx
|
|
1964
|
+
|
|
1965
|
+
def get_distance_voyageai(corpus, query):
|
|
1966
|
+
import voyageai
|
|
1967
|
+
from sentence_transformers import util
|
|
1968
|
+
|
|
1969
|
+
voyageai.api_key = self.open_api_config.get("voyageai_api_key", "")
|
|
1970
|
+
|
|
1971
|
+
def get_embed(text):
|
|
1972
|
+
return voyageai.get_embedding(text, model="voyage-01")
|
|
1973
|
+
|
|
1974
|
+
doc_embeds = [get_embed(i) for i in corpus]
|
|
1975
|
+
query = get_embed(query)
|
|
1976
|
+
doc_scores = -util.dot_score(doc_embeds, query).cpu().numpy().reshape(-1)
|
|
1977
|
+
idx = [(ii, 0) for ii in np.argsort(doc_scores)]
|
|
1978
|
+
return idx
|
|
1979
|
+
|
|
1980
|
+
def get_distance_cohere(corpus, query):
|
|
1981
|
+
import cohere
|
|
1982
|
+
|
|
1983
|
+
api_key = self.open_api_config.get("cohere_api_key", "")
|
|
1984
|
+
co = cohere.Client(api_key)
|
|
1985
|
+
results = co.rerank(model="rerank-english-v2.0", query=query, documents=corpus, top_n=20)
|
|
1986
|
+
c_map = {jj: ii for ii, jj in enumerate(corpus)}
|
|
1987
|
+
doc_rank = [c_map[ii.document["text"]] for ii in results]
|
|
1988
|
+
idx = [(ii, 0) for ii in doc_rank]
|
|
1989
|
+
return idx
|
|
1990
|
+
|
|
1991
|
+
def get_distance_longllmlingua(corpus, query):
|
|
1992
|
+
context_ppl = [
|
|
1993
|
+
self.get_condition_ppl(
|
|
1994
|
+
d,
|
|
1995
|
+
query + " We can get the answer to this question in the given documents.",
|
|
1996
|
+
condition_in_question,
|
|
1997
|
+
)
|
|
1998
|
+
- dl * 2 / 250 * 0
|
|
1999
|
+
for d, dl in zip(corpus, context_tokens_length)
|
|
2000
|
+
]
|
|
2001
|
+
sort_direct = -1 if condition_in_question == "none" else 1
|
|
2002
|
+
ys = sorted(enumerate(context_ppl), key=lambda x: sort_direct * x[1])
|
|
2003
|
+
return ys
|
|
2004
|
+
|
|
2005
|
+
method = None
|
|
2006
|
+
if rank_method == "bm25":
|
|
2007
|
+
method = get_distance_bm25
|
|
2008
|
+
elif rank_method == "gzip":
|
|
2009
|
+
method = get_distance_gzip
|
|
2010
|
+
elif rank_method == "sentbert":
|
|
2011
|
+
method = get_distance_sentbert
|
|
2012
|
+
elif rank_method == "openai":
|
|
2013
|
+
method = get_distance_openai
|
|
2014
|
+
elif rank_method in ["longllmlingua", "llmlingua"]:
|
|
2015
|
+
method = get_distance_longllmlingua
|
|
2016
|
+
elif rank_method == "bge":
|
|
2017
|
+
method = get_distance_sentbert_bge
|
|
2018
|
+
elif rank_method == "bge_reranker":
|
|
2019
|
+
method = get_distance_bge_ranker
|
|
2020
|
+
elif rank_method == "bge_llmembedder":
|
|
2021
|
+
method = get_distance_bge_llmembedder
|
|
2022
|
+
elif rank_method == "jinza":
|
|
2023
|
+
method = get_distance_jinza
|
|
2024
|
+
elif rank_method == "voyageai":
|
|
2025
|
+
method = get_distance_voyageai
|
|
2026
|
+
elif rank_method == "cohere":
|
|
2027
|
+
method = get_distance_cohere
|
|
2028
|
+
return method(context, question)
|
|
2029
|
+
|
|
2030
|
+
def segment_structured_context(
|
|
2031
|
+
self,
|
|
2032
|
+
context: List[str],
|
|
2033
|
+
global_rate: float,
|
|
2034
|
+
):
|
|
2035
|
+
new_context, context_segs, context_segs_rate, context_segs_compress = (
|
|
2036
|
+
[],
|
|
2037
|
+
[],
|
|
2038
|
+
[],
|
|
2039
|
+
[],
|
|
2040
|
+
)
|
|
2041
|
+
for text in context:
|
|
2042
|
+
if not text.startswith("<llmlingua"):
|
|
2043
|
+
text = "<llmlingua>" + text
|
|
2044
|
+
if not text.endswith("</llmlingua>"):
|
|
2045
|
+
text = text + "</llmlingua>"
|
|
2046
|
+
|
|
2047
|
+
# Regular expression to match <llmlingua, rate=x, compress=y>content</llmlingua>, allowing rate and compress in any order
|
|
2048
|
+
pattern = r"<llmlingua\s*(?:,\s*rate\s*=\s*([\d\.]+))?\s*(?:,\s*compress\s*=\s*(True|False))?\s*(?:,\s*rate\s*=\s*([\d\.]+))?\s*(?:,\s*compress\s*=\s*(True|False))?\s*>([^<]+)</llmlingua>"
|
|
2049
|
+
matches = re.findall(pattern, text)
|
|
2050
|
+
|
|
2051
|
+
# Extracting segment contents
|
|
2052
|
+
segments = [match[4] for match in matches]
|
|
2053
|
+
|
|
2054
|
+
# Extracting rate and compress, considering their possible positions
|
|
2055
|
+
segs_rate = [float(match[0]) if match[0] else (float(match[2]) if match[2] else None) for match in matches]
|
|
2056
|
+
segs_compress = [
|
|
2057
|
+
(match[1] == "True" if match[1] else (match[3] == "True" if match[3] else None)) for match in matches
|
|
2058
|
+
]
|
|
2059
|
+
|
|
2060
|
+
segs_compress = [compress if compress is not None else True for compress in segs_compress]
|
|
2061
|
+
segs_rate = [
|
|
2062
|
+
rate if rate else (global_rate if compress else 1.0) for rate, compress in zip(segs_rate, segs_compress)
|
|
2063
|
+
]
|
|
2064
|
+
assert (
|
|
2065
|
+
len(segments) == len(segs_rate) == len(segs_compress)
|
|
2066
|
+
), "The number of segments, rates, and compress flags should be the same."
|
|
2067
|
+
assert all(
|
|
2068
|
+
seg_rate <= 1.0 for seg_rate in segs_rate
|
|
2069
|
+
), "Error: 'rate' must not exceed 1.0. The value of 'rate' indicates compression rate and must be within the range [0, 1]."
|
|
2070
|
+
|
|
2071
|
+
new_context.append("".join(segments))
|
|
2072
|
+
context_segs.append(segments)
|
|
2073
|
+
context_segs_rate.append(segs_rate)
|
|
2074
|
+
context_segs_compress.append(segs_compress)
|
|
2075
|
+
|
|
2076
|
+
return new_context, context_segs, context_segs_rate, context_segs_compress
|
|
2077
|
+
|
|
2078
|
+
def concate_segment_info(
|
|
2079
|
+
self,
|
|
2080
|
+
segment_info: List[List[tuple]],
|
|
2081
|
+
):
|
|
2082
|
+
new_segment_info = []
|
|
2083
|
+
for i, (seg_len, seg_ratio, seg_compress) in enumerate(segment_info):
|
|
2084
|
+
if new_segment_info and new_segment_info[-1][1] == seg_ratio and new_segment_info[-1][2] == seg_compress:
|
|
2085
|
+
new_segment_info[-1] = (
|
|
2086
|
+
new_segment_info[-1][0] + seg_len,
|
|
2087
|
+
seg_ratio,
|
|
2088
|
+
seg_compress,
|
|
2089
|
+
)
|
|
2090
|
+
else:
|
|
2091
|
+
new_segment_info.append((seg_len, seg_ratio, seg_compress))
|
|
2092
|
+
return new_segment_info
|
|
2093
|
+
|
|
2094
|
+
def __get_context_prob(
|
|
2095
|
+
self,
|
|
2096
|
+
context_list: list,
|
|
2097
|
+
token_to_word="mean",
|
|
2098
|
+
force_tokens: List[str] = [],
|
|
2099
|
+
token_map: dict = {},
|
|
2100
|
+
force_reserve_digit: bool = False,
|
|
2101
|
+
):
|
|
2102
|
+
chunk_list = []
|
|
2103
|
+
for chunks in context_list:
|
|
2104
|
+
for c in chunks:
|
|
2105
|
+
chunk_list.append(c)
|
|
2106
|
+
|
|
2107
|
+
dataset = TokenClfDataset(chunk_list, tokenizer=self.tokenizer, max_len=self.max_seq_len)
|
|
2108
|
+
dataloader = DataLoader(dataset, batch_size=self.max_batch_size, shuffle=False, drop_last=False)
|
|
2109
|
+
|
|
2110
|
+
chunk_probs = []
|
|
2111
|
+
chunk_words = []
|
|
2112
|
+
with torch.inference_mode(mode=True):
|
|
2113
|
+
for batch in dataloader:
|
|
2114
|
+
ids = batch["ids"].to(self.device, dtype=torch.long)
|
|
2115
|
+
mask = batch["mask"].to(self.device, dtype=torch.long) == 1
|
|
2116
|
+
|
|
2117
|
+
outputs = self.model(input_ids=ids, attention_mask=mask)
|
|
2118
|
+
loss, logits = outputs.loss, outputs.logits
|
|
2119
|
+
probs = F.softmax(logits, dim=-1)
|
|
2120
|
+
|
|
2121
|
+
for j in range(ids.shape[0]):
|
|
2122
|
+
_probs = probs[j, :, 1]
|
|
2123
|
+
_ids = ids[j]
|
|
2124
|
+
_mask = mask[j]
|
|
2125
|
+
|
|
2126
|
+
active_probs = torch.masked_select(_probs, _mask)
|
|
2127
|
+
active_ids = torch.masked_select(_ids, _mask)
|
|
2128
|
+
|
|
2129
|
+
tokens = self.tokenizer.convert_ids_to_tokens(active_ids.squeeze().tolist())
|
|
2130
|
+
token_probs = [prob for prob in active_probs.cpu().numpy()]
|
|
2131
|
+
|
|
2132
|
+
(
|
|
2133
|
+
words,
|
|
2134
|
+
valid_token_probs,
|
|
2135
|
+
valid_token_probs_no_force,
|
|
2136
|
+
) = self.__merge_token_to_word(
|
|
2137
|
+
tokens,
|
|
2138
|
+
token_probs,
|
|
2139
|
+
force_tokens=force_tokens,
|
|
2140
|
+
token_map=token_map,
|
|
2141
|
+
force_reserve_digit=force_reserve_digit,
|
|
2142
|
+
)
|
|
2143
|
+
word_probs_no_force = self.__token_prob_to_word_prob(
|
|
2144
|
+
valid_token_probs_no_force, convert_mode=token_to_word
|
|
2145
|
+
)
|
|
2146
|
+
|
|
2147
|
+
if "xlm-roberta-large" in self.model_name:
|
|
2148
|
+
for i in range(len(words)):
|
|
2149
|
+
words[i] = words[i].lstrip("▁")
|
|
2150
|
+
chunk_words.append(words)
|
|
2151
|
+
chunk_probs.append(word_probs_no_force)
|
|
2152
|
+
|
|
2153
|
+
prev_idx = 0
|
|
2154
|
+
context_probs = []
|
|
2155
|
+
context_words = []
|
|
2156
|
+
for chunk_list in context_list:
|
|
2157
|
+
n_chunk = len(chunk_list)
|
|
2158
|
+
context_probs.append([])
|
|
2159
|
+
context_words.append([])
|
|
2160
|
+
for i in range(n_chunk):
|
|
2161
|
+
context_probs[-1].extend(chunk_probs[prev_idx + i])
|
|
2162
|
+
context_words[-1].extend(chunk_words[prev_idx + i])
|
|
2163
|
+
prev_idx = prev_idx + n_chunk
|
|
2164
|
+
context_probs = [sum(probs) / len(probs) for probs in context_probs]
|
|
2165
|
+
return context_probs, context_words
|
|
2166
|
+
|
|
2167
|
+
def __chunk_context(self, origin_text, chunk_end_tokens):
|
|
2168
|
+
# leave 2 token for CLS and SEP
|
|
2169
|
+
max_len = self.max_seq_len - 2
|
|
2170
|
+
origin_list = []
|
|
2171
|
+
origin_tokens = self.tokenizer.tokenize(origin_text)
|
|
2172
|
+
n = len(origin_tokens)
|
|
2173
|
+
st = 0
|
|
2174
|
+
while st < n:
|
|
2175
|
+
if st + max_len > n - 1:
|
|
2176
|
+
chunk = self.tokenizer.convert_tokens_to_string(origin_tokens[st:n])
|
|
2177
|
+
origin_list.append(chunk)
|
|
2178
|
+
break
|
|
2179
|
+
else:
|
|
2180
|
+
ed = st + max_len
|
|
2181
|
+
for j in range(0, ed - st):
|
|
2182
|
+
if origin_tokens[ed - j] in chunk_end_tokens:
|
|
2183
|
+
ed = ed - j
|
|
2184
|
+
break
|
|
2185
|
+
chunk = self.tokenizer.convert_tokens_to_string(origin_tokens[st : ed + 1])
|
|
2186
|
+
origin_list.append(chunk)
|
|
2187
|
+
st = ed + 1
|
|
2188
|
+
return origin_list
|
|
2189
|
+
|
|
2190
|
+
def __merge_token_to_word(self, tokens, token_probs, force_tokens, token_map, force_reserve_digit):
|
|
2191
|
+
words = []
|
|
2192
|
+
word_probs = []
|
|
2193
|
+
word_probs_no_force = []
|
|
2194
|
+
|
|
2195
|
+
for token, prob in zip(tokens, token_probs):
|
|
2196
|
+
if token in self.special_tokens:
|
|
2197
|
+
continue
|
|
2198
|
+
# add a new word
|
|
2199
|
+
elif is_begin_of_new_word(token, self.model_name, force_tokens, token_map):
|
|
2200
|
+
pure_token = get_pure_token(token, self.model_name)
|
|
2201
|
+
prob_no_force = prob
|
|
2202
|
+
if pure_token in force_tokens or pure_token in set(token_map.values()):
|
|
2203
|
+
prob = 1.0
|
|
2204
|
+
token = replace_added_token(token, token_map)
|
|
2205
|
+
words.append(token)
|
|
2206
|
+
word_probs.append([1.0 if force_reserve_digit and bool(re.search(r"\d", token)) else prob])
|
|
2207
|
+
word_probs_no_force.append([prob_no_force])
|
|
2208
|
+
# concatenate with previous token
|
|
2209
|
+
else:
|
|
2210
|
+
pure_token = get_pure_token(token, self.model_name)
|
|
2211
|
+
words[-1] += pure_token
|
|
2212
|
+
word_probs[-1].append(1.0 if force_reserve_digit and bool(re.search(r"\d", token)) else prob)
|
|
2213
|
+
word_probs_no_force[-1].append(prob_no_force)
|
|
2214
|
+
|
|
2215
|
+
return words, word_probs, word_probs_no_force
|
|
2216
|
+
|
|
2217
|
+
def __token_prob_to_word_prob(self, token_probs, convert_mode="mean"):
|
|
2218
|
+
if convert_mode == "mean":
|
|
2219
|
+
word_probs = [sum(p) / len(p) for p in token_probs]
|
|
2220
|
+
elif convert_mode == "first":
|
|
2221
|
+
word_probs = [p[0] for p in token_probs]
|
|
2222
|
+
else:
|
|
2223
|
+
raise NotImplementedError()
|
|
2224
|
+
|
|
2225
|
+
return word_probs
|
|
2226
|
+
|
|
2227
|
+
def __compress(
|
|
2228
|
+
self,
|
|
2229
|
+
context_list: list,
|
|
2230
|
+
reduce_rate: float = 0.5,
|
|
2231
|
+
token_to_word: str = "mean",
|
|
2232
|
+
force_tokens: List[str] = [],
|
|
2233
|
+
token_map: dict = {},
|
|
2234
|
+
force_reserve_digit: bool = False,
|
|
2235
|
+
drop_consecutive: bool = False,
|
|
2236
|
+
):
|
|
2237
|
+
def split_string_to_words(input_string):
|
|
2238
|
+
pattern = r'\b\w+\b|[<>=/!@#$%^&*()?":{}|\\`~;_+-]'
|
|
2239
|
+
result = re.findall(pattern, input_string)
|
|
2240
|
+
return result
|
|
2241
|
+
|
|
2242
|
+
if reduce_rate <= 0:
|
|
2243
|
+
words, word_labels = [], []
|
|
2244
|
+
for i in range(len(context_list)):
|
|
2245
|
+
chunk_list = context_list[i]
|
|
2246
|
+
chunk_words = []
|
|
2247
|
+
chunk_word_labels = []
|
|
2248
|
+
for j in range(len(chunk_list)):
|
|
2249
|
+
# replace to original token
|
|
2250
|
+
for ori_token, new_token in token_map.items():
|
|
2251
|
+
chunk_list[j] = chunk_list[j].replace(new_token, ori_token)
|
|
2252
|
+
ws = split_string_to_words(chunk_list[j])
|
|
2253
|
+
chunk_words.extend(ws)
|
|
2254
|
+
chunk_word_labels.extend([1 for _ in range(len(ws))])
|
|
2255
|
+
context_list[i] = "".join(chunk_list)
|
|
2256
|
+
words.append(chunk_words)
|
|
2257
|
+
word_labels.append(chunk_word_labels)
|
|
2258
|
+
return context_list, words, word_labels
|
|
2259
|
+
|
|
2260
|
+
chunk_list = []
|
|
2261
|
+
for chunks in context_list:
|
|
2262
|
+
for c in chunks:
|
|
2263
|
+
chunk_list.append(c)
|
|
2264
|
+
|
|
2265
|
+
dataset = TokenClfDataset(chunk_list, tokenizer=self.tokenizer, max_len=self.max_seq_len)
|
|
2266
|
+
dataloader = DataLoader(dataset, batch_size=self.max_batch_size, shuffle=False, drop_last=False)
|
|
2267
|
+
|
|
2268
|
+
compressed_chunk_list = []
|
|
2269
|
+
word_list = []
|
|
2270
|
+
word_label_list = []
|
|
2271
|
+
with torch.inference_mode(mode=True):
|
|
2272
|
+
for batch in dataloader:
|
|
2273
|
+
ids = batch["ids"].to(self.device, dtype=torch.long)
|
|
2274
|
+
mask = batch["mask"].to(self.device, dtype=torch.long) == 1
|
|
2275
|
+
|
|
2276
|
+
outputs = self.model(input_ids=ids, attention_mask=mask)
|
|
2277
|
+
loss, logits = outputs.loss, outputs.logits
|
|
2278
|
+
probs = F.softmax(logits, dim=-1)
|
|
2279
|
+
|
|
2280
|
+
for j in range(ids.shape[0]):
|
|
2281
|
+
chunk_probs = probs[j, :, 1]
|
|
2282
|
+
chunk_ids = ids[j]
|
|
2283
|
+
chunk_mask = mask[j]
|
|
2284
|
+
|
|
2285
|
+
active_probs = torch.masked_select(chunk_probs, chunk_mask)
|
|
2286
|
+
active_ids = torch.masked_select(chunk_ids, chunk_mask)
|
|
2287
|
+
|
|
2288
|
+
tokens = self.tokenizer.convert_ids_to_tokens(active_ids.squeeze().tolist())
|
|
2289
|
+
token_probs = [prob for prob in active_probs.cpu().numpy()]
|
|
2290
|
+
|
|
2291
|
+
words, valid_token_probs, _ = self.__merge_token_to_word(
|
|
2292
|
+
tokens=tokens,
|
|
2293
|
+
token_probs=token_probs,
|
|
2294
|
+
force_tokens=force_tokens,
|
|
2295
|
+
token_map=token_map,
|
|
2296
|
+
force_reserve_digit=force_reserve_digit,
|
|
2297
|
+
)
|
|
2298
|
+
word_probs = self.__token_prob_to_word_prob(valid_token_probs, convert_mode=token_to_word)
|
|
2299
|
+
|
|
2300
|
+
if drop_consecutive:
|
|
2301
|
+
threshold = np.percentile(word_probs, int(100 * reduce_rate))
|
|
2302
|
+
is_token_between = False
|
|
2303
|
+
prev = None
|
|
2304
|
+
for i, (word, word_prob) in enumerate(zip(words, word_probs)):
|
|
2305
|
+
if word in force_tokens:
|
|
2306
|
+
if is_token_between:
|
|
2307
|
+
is_token_between = False
|
|
2308
|
+
elif not is_token_between and word == prev:
|
|
2309
|
+
word_probs[i] = 0.0
|
|
2310
|
+
prev = word
|
|
2311
|
+
else:
|
|
2312
|
+
is_token_between |= word_prob > threshold
|
|
2313
|
+
|
|
2314
|
+
new_token_probs = []
|
|
2315
|
+
for word, word_prob in zip(words, word_probs):
|
|
2316
|
+
num_token = len(self.oai_tokenizer.encode(word))
|
|
2317
|
+
new_token_probs.extend([word_prob for _ in range(num_token)])
|
|
2318
|
+
threshold = np.percentile(new_token_probs, int(100 * reduce_rate + 1))
|
|
2319
|
+
|
|
2320
|
+
keep_words = []
|
|
2321
|
+
word_labels = []
|
|
2322
|
+
assert len(words) == len(word_probs)
|
|
2323
|
+
for word, word_porb in zip(words, word_probs):
|
|
2324
|
+
if word_porb > threshold:
|
|
2325
|
+
if (
|
|
2326
|
+
drop_consecutive
|
|
2327
|
+
and word in force_tokens
|
|
2328
|
+
and len(keep_words) > 0
|
|
2329
|
+
and keep_words[-1] == word
|
|
2330
|
+
):
|
|
2331
|
+
word_labels.append(0)
|
|
2332
|
+
else:
|
|
2333
|
+
keep_words.append(word)
|
|
2334
|
+
word_labels.append(1)
|
|
2335
|
+
else:
|
|
2336
|
+
word_labels.append(0)
|
|
2337
|
+
keep_str = self.tokenizer.convert_tokens_to_string(keep_words)
|
|
2338
|
+
if "xlm-roberta-large" in self.model_name:
|
|
2339
|
+
for i in range(len(words)):
|
|
2340
|
+
words[i] = words[i].lstrip("▁")
|
|
2341
|
+
|
|
2342
|
+
compressed_chunk_list.append(keep_str)
|
|
2343
|
+
word_list.append(words[:])
|
|
2344
|
+
word_label_list.append(word_labels[:])
|
|
2345
|
+
|
|
2346
|
+
compressed_context_list = []
|
|
2347
|
+
original_word_list = []
|
|
2348
|
+
original_word_label_list = []
|
|
2349
|
+
prev_idx = 0
|
|
2350
|
+
for chunk_list in context_list:
|
|
2351
|
+
n_chunk = len(chunk_list)
|
|
2352
|
+
compressed_context_list.append("".join(compressed_chunk_list[prev_idx : prev_idx + n_chunk]))
|
|
2353
|
+
original_word_list.append([])
|
|
2354
|
+
original_word_label_list.append([])
|
|
2355
|
+
for i in range(n_chunk):
|
|
2356
|
+
original_word_list[-1].extend(word_list[prev_idx + i])
|
|
2357
|
+
original_word_label_list[-1].extend(word_label_list[prev_idx + i])
|
|
2358
|
+
prev_idx = prev_idx + n_chunk
|
|
2359
|
+
|
|
2360
|
+
return compressed_context_list, original_word_list, original_word_label_list
|