qminh369/Compression_v1
0
1# Copyright (c) 2023 Microsoft2# Licensed under The MIT License [see LICENSE for details]3 4import bisect5import copy6import re7import string8from collections import defaultdict9from typing import List10 11import nltk12import numpy as np13import tiktoken14import torch15import torch.nn.functional as F16from torch.utils.data import DataLoader17from transformers import (18 AutoConfig,19 AutoModelForCausalLM,20 AutoModelForTokenClassification,21 AutoTokenizer,22)23 24from core_utils_llmlingua2 import (TokenClfDataset, get_pure_token, is_begin_of_new_word, replace_added_token, seed_everything,)25#from core_utils_llmlingua2_phobert import (TokenClfDataset, get_pure_token, is_begin_of_new_word, replace_added_token, seed_everything,)26 27 28class PromptCompressor:29 """30 PromptCompressor is designed for compressing prompts based on a given language model.31 32 This class initializes with the language model and its configuration, preparing it for prompt compression tasks.33 The PromptCompressor class is versatile and can be adapted for various models and specific requirements in prompt processing.34 Users can specify different model names and configurations as needed for their particular use case.The architecture is35 based on the paper "LLMLingua: Compressing Prompts for Accelerated Inference of Large Language Models". Jiang, Huiqiang, Qianhui Wu,36 Chin-Yew Lin, Yuqing Yang, and Lili Qiu. "Llmlingua: Compressing prompts for accelerated inference of large language models."37 arXiv preprint arXiv:2310.05736 (2023).38 39 Args:40 model_name (str, optional): The name of the language model to be loaded. Default is "NousResearch/Llama-2-7b-hf".41 device_map (str, optional): The device to load the model onto, e.g., "cuda" for GPU. Default is "cuda".42 model_config (dict, optional): A dictionary containing the configuration parameters for the model. Default is an empty dictionary.43 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.44 use_llmlingua2 (bool, optional): Whether to use llmlingua-2 compressor based on the paper45 "LLMLingua-2: Context-Aware Data Distillation for Efficient and Faithful Task-Agnostic Prompt Compression".46 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.47 "LLMLingua-2: Context-Aware Data Distillation for Efficient and Faithful Task-Agnostic Prompt Compression". arXiv preprint arXiv:,48 Default is True.49 llmlingua2_config (dict, optional): A dictionary containing the configuration parameters for llmlingua-2. Default is50 {51 "max_batch_size": 50,52 "max_force_token": 100, # max number of the tokens which will be forcely preserved53 }54 Example:55 >>> compress_method = PromptCompressor(model_name="microsoft/llmlingua-2-xlm-roberta-large-meetingbank", use_llmlingua2=True, )56 >>> context = ["This is the first context sentence.", "Here is another context sentence."]57 >>> result = compress_method.compress_prompt(context, use_context_level_filter=True, target_token=5)58 >>> print(result["compressed_prompt"])59 # This will print the compressed version of the context.60 61 Note:62 The `PromptCompressor` class requires the Hugging Face Transformers library and an appropriate environment to load and run the models.63 """64 65 def __init__(66 self,67 model_name: str = "NousResearch/Llama-2-7b-hf",68 device_map: str = "cuda",69 model_config: dict = {},70 open_api_config: dict = {},71 use_llmlingua2: bool = False,72 llmlingua2_config: dict = {},73 ):74 self.model_name = model_name75 self.use_llmlingua2 = use_llmlingua276 self.retrieval_model = None77 self.retrieval_model_name = None78 self.open_api_config = open_api_config79 self.cache_bos_num = 1080 self.prefix_bos_num = 10081 self.oai_tokenizer = tiktoken.encoding_for_model("gpt-3.5-turbo")82 83 self.load_model(model_name, device_map, model_config)84 if use_llmlingua2:85 self.init_llmlingua2(**llmlingua2_config)86 87 def init_llmlingua2(88 self,89 max_batch_size: int = 50,90 max_force_token: int = 100,91 ):92 seed_everything(42)93 self.max_batch_size = max_batch_size94 self.max_seq_len = 512 # 512 (xlm-roberta) 256 (phobert)95 self.max_force_token = max_force_token96 self.special_tokens = set( # trả ra special tokens97 [98 v99 for k, v in self.tokenizer.special_tokens_map.items()100 if k != "additional_special_tokens"101 ]102 )103 104 self.added_tokens = [f"[NEW{i}]" for i in range(max_force_token)]105 self.tokenizer.add_special_tokens( # Add special token in force token106 {"additional_special_tokens": self.added_tokens}107 )108 self.model.resize_token_embeddings(len(self.tokenizer)) # Resize embedding dim109 110 def load_model(111 self, model_name: str, device_map: str = "cuda", model_config: dict = {}112 ):113 trust_remote_code = model_config.get("trust_remote_code", True)114 if "trust_remote_code" not in model_config:115 model_config["trust_remote_code"] = trust_remote_code116 config = AutoConfig.from_pretrained(model_name, **model_config)117 tokenizer = AutoTokenizer.from_pretrained(model_name, **model_config)118 if model_config.get("pad_to_left", True):119 tokenizer.padding_side = "left"120 tokenizer.pad_token_id = (121 config.pad_token_id if config.pad_token_id else tokenizer.eos_token_id122 )123 MODEL_CLASS = (124 AutoModelForTokenClassification # Use llmlingua2125 if any("ForTokenClassification" in ar for ar in config.architectures)126 else AutoModelForCausalLM127 )128 self.device = (129 device_map130 if any(key in device_map for key in ["cuda", "cpu", "mps"])131 else "cuda"132 )133 if "cuda" in device_map or "cpu" in device_map:134 model = MODEL_CLASS.from_pretrained(135 model_name,136 torch_dtype=model_config.get(137 "torch_dtype", "auto" if device_map == "cuda" else torch.float32138 ),139 device_map=device_map,140 config=config,141 ignore_mismatched_sizes=True,142 **model_config,143 )144 else:145 model = MODEL_CLASS.from_pretrained(146 model_name,147 device_map=device_map,148 torch_dtype=model_config.get("torch_dtype", "auto"),149 pad_token_id=tokenizer.pad_token_id,150 **model_config,151 )152 self.tokenizer = tokenizer153 self.model = model154 self.context_idxs = []155 self.max_position_embeddings = config.max_position_embeddings156 157 def get_ppl(158 self,159 text: str,160 granularity: str = "sentence",161 input_ids=None,162 attention_mask=None,163 past_key_values=None,164 return_kv=False,165 end=None,166 condition_mode: str = "none",167 condition_pos_id: int = 0,168 ):169 if input_ids is None:170 tokenized_text = self.tokenizer(text, return_tensors="pt")171 input_ids = tokenized_text["input_ids"].to(self.device)172 attention_mask = tokenized_text["attention_mask"].to(self.device)173 if past_key_values is not None:174 past_length = past_key_values[0][0].shape[2]175 else:176 past_length = 0177 if end is None:178 end = input_ids.shape[1]179 end = min(end, past_length + self.max_position_embeddings)180 with torch.no_grad():181 response = self.model(182 input_ids[:, past_length:end],183 attention_mask=attention_mask[:, :end],184 past_key_values=past_key_values,185 use_cache=True,186 )187 past_key_values = response.past_key_values188 189 shift_logits = response.logits[..., :-1, :].contiguous()190 shift_labels = input_ids[..., past_length + 1 : end].contiguous()191 # Flatten the tokens192 active = (attention_mask[:, past_length:end] == 1)[..., :-1].view(-1)193 active_logits = shift_logits.view(-1, shift_logits.size(-1))[active]194 active_labels = shift_labels.view(-1)[active]195 loss_fct = torch.nn.CrossEntropyLoss(reduction="none")196 loss = loss_fct(active_logits, active_labels)197 if condition_mode == "before":198 loss = loss[:condition_pos_id]199 elif condition_mode == "after":200 loss = loss[condition_pos_id:]201 res = loss.mean() if granularity == "sentence" else loss202 return (res, past_key_values) if return_kv else res203 204 def __call__(self, *args, **kwargs):205 return self.compress_prompt(*args, **kwargs)206 207 def structured_compress_prompt(208 self,209 context: List[str],210 instruction: str = "",211 question: str = "",212 rate: float = 0.5,213 target_token: float = -1,214 iterative_size: int = 200,215 force_context_ids: List[int] = None,216 force_context_number: int = None,217 use_sentence_level_filter: bool = False,218 use_context_level_filter: bool = True,219 use_token_level_filter: bool = True,220 keep_split: bool = False,221 keep_first_sentence: int = 0,222 keep_last_sentence: int = 0,223 keep_sentence_number: int = 0,224 high_priority_bonus: int = 100,225 context_budget: str = "+100",226 token_budget_ratio: float = 1.4,227 condition_in_question: str = "none",228 reorder_context: str = "original",229 dynamic_context_compression_ratio: float = 0.0,230 condition_compare: bool = False,231 add_instruction: bool = False,232 rank_method: str = "llmlingua",233 concate_question: bool = True,234 ):235 """236 Compresses the given prompt context based on a specified structure.237 238 Each element of context should be segmented using one or more non-nested '<llmlingua></llmlingua>' tags.239 Each '<llmlingua>' tag can include optional parameters 'rate' and 'compress' (e.g., '<llmlingua, rate=0.3, compress=True>'),240 indicating the compression rate for that segment. Default values are 'rate=rate' and 'compress=True'.241 When 'compress' is set to False, it overrides the 'rate' parameter, resulting in no compression for that segment.242 243 Args:244 context (List[str]): List of context strings divided by '<llmlingua></llmlingua>' tags with optional compression settings.245 instruction (str, optional): Additional instruction text to be included in the prompt. Default is an empty string.246 question (str, optional): A specific question that the prompt is addressing. Default is an empty string.247 rate (float, optional): The compression rate is defined the same as in paper "Language Modeling Is Compression".248 Delétang, Grégoire, Anian Ruoss, Paul-Ambroise Duquenne, Elliot Catt, Tim Genewein, Christopher Mattern,249 Jordi Grau-Moya et al. "Language modeling is compression." arXiv preprint arXiv:2309.10668 (2023):250 .. math::\text{Compression Rate} = \frac{\text{Compressed Size}}{\text{Raw Size}}251 Default is 0.5. The actual compression rate is generally lower than the specified target, but there can be252 fluctuations due to differences in tokenizers. If specified, it should be a float less than or equal253 to 1.0, representing the target compression rate. ``rate``, is applicable only within the context-level filter254 and the sentence-level filter. In the token-level filter, the rate for each segment overrides the global rate.255 However, for segments where no specific rate is defined, the global rate serves as the default value. The final256 compression rate of the entire text is a composite result of multiple compression rates applied across different sections.257 target_token (float, optional): The global maximum number of tokens to be achieved. Default is -1, indicating no258 specific target. The actual number of tokens after compression should generally be less than the specified target_token,259 but there can be fluctuations due to differences in tokenizers. If specified, compression will be based on the target_token as260 the sole criterion, overriding the ``rate``. ``target_token``, is applicable only within the context-level261 filter and the sentence-level filter. In the token-level filter, the rate for each segment overrides the global target token.262 However, for segments where no specific rate is defined, the global rate calculated from global target token serves263 as the default value. The final target token of the entire text is a composite result of multiple compression rates264 applied across different sections.265 iterative_size (int, optional): The number of tokens to consider in each iteration of compression. Default is 200.266 force_context_ids (List[int], optional): List of specific context IDs to always include in the compressed result. Default is None.267 force_context_number (int, optional): The number of context sections to forcibly include. Default is None.268 use_sentence_level_filter (bool, optional): Whether to apply sentence-level filtering in compression. Default is False.269 use_context_level_filter (bool, optional): Whether to apply context-level filtering in compression. Default is True.270 use_token_level_filter (bool, optional): Whether to apply token-level filtering in compression. Default is True.271 keep_split (bool, optional): Whether to preserve the original separators without compression. Default is False.272 keep_first_sentence (int, optional): Number of sentences to forcibly preserve from the start of the context. Default is 0.273 keep_last_sentence (int, optional): Number of sentences to forcibly preserve from the end of the context. Default is 0.274 keep_sentence_number (int, optional): Total number of sentences to forcibly preserve in the compression. Default is 0.275 high_priority_bonus (int, optional): Bonus score for high-priority sentences to influence their likelihood of being retained. Default is 100.276 context_budget (str, optional): Token budget for the context-level filtering, expressed as a string to indicate flexibility. Default is "+100".277 token_budget_ratio (float, optional): Ratio to adjust token budget during sentence-level filtering. Default is 1.4.278 condition_in_question (str, optional): Specific condition to apply to question in the context. Default is "none".279 reorder_context (str, optional): Strategy for reordering context in the compressed result. Default is "original".280 dynamic_context_compression_ratio (float, optional): Ratio for dynamically adjusting context compression. Default is 0.0.281 condition_compare (bool, optional): Whether to enable condition comparison during token-level compression. Default is False.282 add_instruction (bool, optional): Whether to add the instruction to the prompt prefix. Default is False.283 rank_method (str, optional): Method used for ranking elements during compression. Default is "llmlingua".284 concate_question (bool, optional): Whether to concatenate the question to the compressed prompt. Default is True.285 286 Returns:287 dict: A dictionary containing:288 - "compressed_prompt" (str): The resulting compressed prompt.289 - "origin_tokens" (int): The original number of tokens in the input.290 - "compressed_tokens" (int): The number of tokens in the compressed output.291 - "ratio" (str): The compression ratio achieved, calculated as the original token number divided by the token number after compression.292 - "rate" (str): The compression rate achieved, in a human-readable format.293 - "saving" (str): Estimated savings in GPT-4 token usage.294 """295 if not context:296 context = [" "]297 if isinstance(context, str):298 context = [context]299 context = [300 self.tokenizer.decode(self.tokenizer(c, add_special_tokens=False).input_ids)301 for c in context302 ]303 context_tokens_length = [self.get_token_length(c) for c in context]304 instruction_tokens_length, question_tokens_length = self.get_token_length(305 instruction306 ), self.get_token_length(question)307 if target_token == -1:308 target_token = (309 (310 instruction_tokens_length311 + question_tokens_length312 + sum(context_tokens_length)313 )314 * rate315 - instruction_tokens_length316 - (question_tokens_length if concate_question else 0)317 )318 else:319 rate = target_token / sum(context_tokens_length)320 (321 context,322 context_segs,323 context_segs_rate,324 context_segs_compress,325 ) = self.segment_structured_context(context, rate)326 return self.compress_prompt(327 context,328 instruction,329 question,330 rate,331 target_token,332 iterative_size,333 force_context_ids,334 force_context_number,335 use_sentence_level_filter,336 use_context_level_filter,337 use_token_level_filter,338 keep_split,339 keep_first_sentence,340 keep_last_sentence,341 keep_sentence_number,342 high_priority_bonus,343 context_budget,344 token_budget_ratio,345 condition_in_question,346 reorder_context,347 dynamic_context_compression_ratio,348 condition_compare,349 add_instruction,350 rank_method,351 concate_question,352 context_segs=context_segs,353 context_segs_rate=context_segs_rate,354 context_segs_compress=context_segs_compress,355 )356 357 def compress_prompt(358 self,359 context: List[str],360 instruction: str = "",361 question: str = "",362 # llmlingua1363 rate: float = 0.5,364 target_token: float = -1,365 iterative_size: int = 200,366 force_context_ids: List[int] = None,367 force_context_number: int = None,368 use_sentence_level_filter: bool = False, # hầu như ko dùng369 use_context_level_filter: bool = True,370 use_token_level_filter: bool = True,371 keep_split: bool = False,372 keep_first_sentence: int = 0,373 keep_last_sentence: int = 0,374 keep_sentence_number: int = 0,375 high_priority_bonus: int = 100,376 context_budget: str = "+100",377 token_budget_ratio: float = 1.4,378 condition_in_question: str = "none",379 reorder_context: str = "original",380 dynamic_context_compression_ratio: float = 0.0,381 condition_compare: bool = False,382 add_instruction: bool = False,383 rank_method: str = "llmlingua",384 concate_question: bool = True,385 context_segs: List[str] = None,386 context_segs_rate: List[float] = None,387 context_segs_compress: List[bool] = None,388 # llmlingua2389 target_context: int = -1, # config số lượng context trả về390 context_level_rate: float = 1.0, # config tỉ lệ nén nhỏ nhất khi sử dụng context-level391 context_level_target_token: int = -1, # config số token tối đa khi sử dụng context-level392 return_word_label: bool = False, # config liệu có trả về word trong label393 word_sep: str = "\t\t|\t\t",394 label_sep: str = " ",395 token_to_word: str = "mean", # Config phương pháp sử dụng chuyển từ xác suất token sang xác suất word396 force_tokens: List[str] = [], # Config các tokens luôn được giữ lại trong compressed prompt397 force_reserve_digit: bool = False, # Config liệu có bắt buộc giữ các token là chữ số398 drop_consecutive: bool = False, # Config liệu có loại bỏ các tokens trong force token khi mà các từ này xuất hiện trong compressed prompt399 chunk_end_tokens: List[str] = [".", "\n"], # Config các stop token để segment chunk400 ):401 """402 Compresses the given context.403 404 Args:405 context (List[str]): List of context strings that form the basis of the prompt.406 instruction (str, optional): Additional instruction text to be included in the prompt. Default is an empty string.407 question (str, optional): A specific question that the prompt is addressing. Default is an empty string.408 rate (float, optional): The maximum compression rate target to be achieved. The compression rate is defined409 the same as in paper "Language Modeling Is Compression". Delétang, Grégoire, Anian Ruoss, Paul-Ambroise Duquenne,410 Elliot Catt, Tim Genewein, Christopher Mattern, Jordi Grau-Moya et al. "Language modeling is compression."411 arXiv preprint arXiv:2309.10668 (2023):412 .. math::\text{Compression Rate} = \frac{\text{Compressed Size}}{\text{Raw Size}}413 Default is 0.5. The actual compression rate is generally lower than the specified target, but there can be414 fluctuations due to differences in tokenizers. If specified, it should be a float less than or equal415 to 1.0, representing the target compression rate.416 target_token (float, optional): The maximum number of tokens to be achieved. Default is -1, indicating no specific target.417 The actual number of tokens after compression should generally be less than the specified target_token, but there can418 be fluctuations due to differences in tokenizers. If specified, compression will be based on the target_token as419 the sole criterion, overriding the ``rate``.420 iterative_size (int, optional): The number of tokens to consider in each iteration of compression. Default is 200.421 force_context_ids (List[int], optional): List of specific context IDs to always include in the compressed result. Default is None.422 force_context_number (int, optional): The number of context sections to forcibly include. Default is None.423 use_sentence_level_filter (bool, optional): Whether to apply sentence-level filtering in compression. Default is False.424 use_context_level_filter (bool, optional): Whether to apply context-level filtering in compression. Default is True.425 use_token_level_filter (bool, optional): Whether to apply token-level filtering in compression. Default is True.426 keep_split (bool, optional): Whether to preserve the original separators without compression. Default is False.427 keep_first_sentence (int, optional): Number of sentences to forcibly preserve from the start of the context. Default is 0.428 keep_last_sentence (int, optional): Number of sentences to forcibly preserve from the end of the context. Default is 0.429 keep_sentence_number (int, optional): Total number of sentences to forcibly preserve in the compression. Default is 0.430 high_priority_bonus (int, optional): Bonus score for high-priority sentences to influence their likelihood of being retained. Default is 100.431 context_budget (str, optional): Token budget for the context-level filtering, expressed as a string to indicate flexibility. Default is "+100".432 token_budget_ratio (float, optional): Ratio to adjust token budget during sentence-level filtering. Default is 1.4.433 condition_in_question (str, optional): Specific condition to apply to question in the context. Default is "none".434 reorder_context (str, optional): Strategy for reordering context in the compressed result. Default is "original".435 dynamic_context_compression_ratio (float, optional): Ratio for dynamically adjusting context compression. Default is 0.0.436 condition_compare (bool, optional): Whether to enable condition comparison during token-level compression. Default is False.437 add_instruction (bool, optional): Whether to add the instruction to the prompt prefix. Default is False.438 rank_method (str, optional): Method used for ranking elements during compression. Default is "llmlingua".439 concate_question (bool, optional): Whether to concatenate the question to the compressed prompt. Default is True.440 441 target_context (int, optional): The maximum number of contexts to be achieved. Default is -1, indicating no specific target.442 context_level_rate (float, optional): The minimum compression rate target to be achieved in context level. Default is 1.0.443 context_level_target_token (float, optional): The maximum number of tokens to be achieved in context level compression.444 Default is -1, indicating no specific target. Only used in the coarse-to-fine compression senario.445 force_context_ids (List[int], optional): List of specific context IDs to always include in the compressed result. Default is None.446 return_word_label (bool, optional): Whether to return word with corresponding label. Default is False.447 word_sep (str, optional): The sep token used in fn_labeled_original_prompt to partition words. Default is "\t\t|\t\t".448 label_sep (str, optional): The sep token used in fn_labeled_original_prompt to partition word and label. Default is " ".449 token_to_word (str, optional): How to convert token probability to word probability. Default is "mean".450 force_tokens (List[str], optional): List of specific tokens to always include in the compressed result. Default is [].451 force_reserve_digit (bool, optional): Whether to forcibly reserve tokens that containing digit (0,...,9). Default is False.452 drop_consecutive (bool, optinal): Whether to drop tokens which are in 'force_tokens' but appears consecutively in compressed prompt.453 Default is False.454 chunk_end_tokens (List[str], optinal): The early stop tokens for segmenting chunk. Default is [".", "\n"],455 Returns:456 dict: A dictionary containing:457 - "compressed_prompt" (str): The resulting compressed prompt.458 - "compressed_prompt_list" (List[str]): List of the resulting compressed prompt. Only used in llmlingua2.459 - "fn_labeled_original_prompt" (str): original words along with their labels460 indicating whether to reserve in compressed prompt, in the format (word label_sep label)461 Only used in llmlingua2 when return_word_label = True.462 - "origin_tokens" (int): The original number of tokens in the input.463 - "compressed_tokens" (int): The number of tokens in the compressed output.464 - "ratio" (str): The compression ratio achieved, calculated as the original token number divided by the token number after compression.465 - "rate" (str): The compression rate achieved, in a human-readable format.466 - "saving" (str): Estimated savings in GPT-4 token usage.467 """468 if self.use_llmlingua2: # dùng cả llmlingua2 và llmlingua1469 return self.compress_prompt_llmlingua2(470 context,471 rate=rate,472 target_token=target_token,473 use_context_level_filter=use_context_level_filter, # True474 use_token_level_filter=use_token_level_filter,475 target_context=target_context,476 context_level_rate=context_level_rate,477 context_level_target_token=context_level_target_token,478 force_context_ids=force_context_ids,479 return_word_label=return_word_label,480 word_sep=word_sep,481 label_sep=label_sep,482 token_to_word=token_to_word,483 force_tokens=force_tokens,484 force_reserve_digit=force_reserve_digit,485 drop_consecutive=drop_consecutive,486 chunk_end_tokens=chunk_end_tokens,487 )488 489 # return luôn một hàm là ko chạy tiếp phần sau nữa490 assert (491 rate <= 1.0492 ), "Error: 'rate' must not exceed 1.0. The value of 'rate' indicates compression rate and must be within the range [0, 1]."493 494 if not context:495 context = [" "]496 if isinstance(context, str):497 context = [context]498 assert not (499 rank_method == "longllmlingua" and not question500 ), "In the LongLLMLingua, it is necessary to set a question."501 if condition_compare and "_condition" not in condition_in_question:502 condition_in_question += "_condition"503 if rank_method == "longllmlingua":504 if condition_in_question == "none":505 condition_in_question = "after"506 elif rank_method == "llmlingua":507 condition_in_question = (508 "none"509 if "_condition" not in condition_in_question510 else "none_condition"511 )512 origin_tokens = len(513 self.oai_tokenizer.encode(514 "\n\n".join([instruction] + context + [question]).strip()515 )516 )517 context_tokens_length = [self.get_token_length(c) for c in context]518 instruction_tokens_length, question_tokens_length = self.get_token_length(519 instruction520 ), self.get_token_length(question)521 if target_token == -1:522 target_token = (523 (524 instruction_tokens_length525 + question_tokens_length526 + sum(context_tokens_length)527 )528 * rate529 - instruction_tokens_length530 - (question_tokens_length if concate_question else 0)531 )532 condition_flag = "_condition" in condition_in_question533 condition_in_question = condition_in_question.replace("_condition", "")534 535 if len(context) > 1 and use_context_level_filter:536 context, dynamic_ratio, context_used = self.control_context_budget(537 context,538 context_tokens_length,539 target_token,540 force_context_ids,541 force_context_number,542 question,543 condition_in_question,544 reorder_context=reorder_context,545 dynamic_context_compression_ratio=dynamic_context_compression_ratio,546 rank_method=rank_method,547 context_budget=context_budget,548 context_segs=context_segs,549 context_segs_rate=context_segs_rate,550 context_segs_compress=context_segs_compress,551 )552 #print('Context used: ', context_used)553 if context_segs is not None:554 context_segs = [context_segs[idx] for idx in context_used]555 context_segs_rate = [context_segs_rate[idx] for idx in context_used]556 context_segs_compress = [557 context_segs_compress[idx] for idx in context_used558 ]559 else:560 dynamic_ratio = [0.0] * len(context)561 562 segments_info = []563 if use_sentence_level_filter:564 context, segments_info = self.control_sentence_budget(565 context,566 target_token,567 keep_first_sentence=keep_first_sentence,568 keep_last_sentence=keep_last_sentence,569 keep_sentence_number=keep_sentence_number,570 high_priority_bonus=high_priority_bonus,571 token_budget_ratio=token_budget_ratio,572 question=question,573 condition_in_question=condition_in_question,574 rank_method=rank_method,575 context_segs=context_segs,576 context_segs_rate=context_segs_rate,577 context_segs_compress=context_segs_compress,578 )579 elif context_segs is not None:580 for context_idx in range(len(context)):581 segments_info.append(582 [583 (len(seg_text), seg_rate, seg_compress)584 for seg_text, seg_rate, seg_compress in zip(585 context_segs[context_idx],586 context_segs_rate[context_idx],587 context_segs_compress[context_idx],588 )589 ]590 )591 segments_info = [592 self.concate_segment_info(segment_info) for segment_info in segments_info593 ]594 595 if condition_flag:596 prefix = question + "\n\n" + instruction if add_instruction else question597 if (598 self.get_token_length(prefix + "\n\n") + iterative_size * 2599 > self.max_position_embeddings600 ):601 tokens = self.tokenizer(prefix, add_special_tokens=False).input_ids602 prefix = self.tokenizer.decode(603 tokens[: self.prefix_bos_num]604 + tokens[605 len(tokens)606 - self.max_position_embeddings607 + 2608 + self.prefix_bos_num609 + 2 * iterative_size :610 ]611 )612 start = self.get_prefix_length(prefix + "\n\n", context[0])613 context = [prefix] + context614 else:615 start = 0616 617 #print('Context level: ', context)618 619 if use_token_level_filter:620 context = self.iterative_compress_prompt(621 context,622 target_token,623 iterative_size=iterative_size,624 keep_split=keep_split,625 start=start,626 dynamic_ratio=dynamic_ratio,627 condition_compare=condition_compare,628 segments_info=segments_info,629 )630 compressed_prompt = (631 self.tokenizer.batch_decode(context[0])[0]632 .replace("<s> ", "")633 .replace("<s>", "")634 )635 else:636 if condition_flag:637 context = context[1:]638 compressed_prompt = "\n\n".join(context)639 #compressed_prompt = " ".join(context)640 641 642 compressed_prompt = "\n\n".join(context) # gồm cả context của 2 loại level643 #compressed_prompt = " ".join(context)644 res = []645 if instruction:646 res.append(instruction)647 if compressed_prompt.strip():648 res.append(compressed_prompt)649 if question and concate_question:650 res.append(question)651 652 compressed_prompt = "\n\n".join(res)653 #compressed_prompt = " ".join(res)654 655 compressed_tokens = len(self.oai_tokenizer.encode(compressed_prompt))656 saving = (origin_tokens - compressed_tokens) * 0.06 / 1000657 ratio = 1 if compressed_tokens == 0 else origin_tokens / compressed_tokens658 rate = 1 / ratio659 return {660 "compressed_prompt": compressed_prompt,661 "origin_tokens": origin_tokens,662 "compressed_tokens": compressed_tokens,663 "ratio": f"{ratio:.1f}x",664 "rate": f"{rate * 100:.1f}%",665 "saving": f", Saving ${saving:.1f} in GPT-4.",666 }667 668 def compress_prompt_llmlingua2(669 self,670 context: List[str],671 rate: float = 0.5,672 target_token: int = -1,673 use_context_level_filter: bool = False, # True674 use_token_level_filter: bool = True,675 target_context: int = -1,676 context_level_rate: float = 1.0,677 context_level_target_token: int = -1,678 force_context_ids: List[int] = [],679 return_word_label: bool = False,680 word_sep: str = "\t\t|\t\t",681 label_sep: str = " ",682 token_to_word: str = "mean",683 force_tokens: List[str] = [],684 force_reserve_digit: bool = False,685 drop_consecutive: bool = False,686 chunk_end_tokens: List[str] = [".", "\n"],687 ):688 """689 Compresses the given context, instruction and question.690 691 Args:692 context (List[str]): List of context strings that form the basis of the prompt.693 rate (float, optional): The minimum compression rate target to be achieved. Default is 0.5. The actual compression rate694 generally exceeds the specified target, but there can be fluctuations due to differences in tokenizers. If specified,695 it should be a float greater than or equal to 1.0, representing the target compression rate.696 target_token (int, optional): The maximum number of tokens to be achieved. Default is -1, indicating no specific target.697 The actual number of tokens after compression should generally be less than the specified target_token, but there can698 be fluctuations due to differences in tokenizers. If specified, compression will be based on the target_token as699 the sole criterion, overriding the rate.700 target_context (int, optional): The maximum number of contexts to be achieved. Default is -1, indicating no specific target.701 Only used in the coarse-to-fine compression.702 context_level_rate (float, optional): The minimum compression rate target to be achieved in context level. Default is 1.0.703 Only used in the coarse-to-fine compression.704 context_level_target_token (float, optional): The maximum number of tokens to be achieved in context level compression.705 Default is -1, indicating no specific target. Only used in the coarse-to-fine compression senario.706 force_context_ids (List[int], optional): List of specific context IDs to always include in the compressed result. Default is None.707 return_word_label (bool, optional): Whether to return word with corresponding label. Default is False.708 word_sep (str, optional): The sep token used in fn_labeled_original_prompt to partition words. Default is "\t\t|\t\t".709 label_sep (str, optional): The sep token used in fn_labeled_original_prompt to partition word and label. Default is " ".710 token_to_word (str, optional): How to convert token probability to word probability. Default is "mean".711 force_tokens (List[str], optional): List of specific tokens to always include in the compressed result. Default is [].712 force_reserve_digit (bool, optional): Whether to forcibly reserve tokens that containing digit (0,...,9). Default is False.713 drop_consecutive (bool, optinal): Whether to drop tokens which are in 'force_tokens' but appears consecutively in compressed prompt.714 Default is False.715 chunk_end_tokens (List[str], optional): The early stop tokens for segmenting chunk. Default is [".", "\n"].716 Returns:717 dict: A dictionary containing:718 - "compressed_prompt" (str): The resulting compressed prompt.719 - "compressed_prompt_list" (List[str]): List of the resulting compressed prompt. (compress cho từng chụnk)720 - "fn_labeled_original_prompt" (str): original words along with their labels (các từ được giữ lại)721 indicating whether to reserve in compressed prompt, in the format (word label_sep label)722 - "origin_tokens" (int): The original number of tokens in the input.723 - "compressed_tokens" (int): The number of tokens in the compressed output.724 - "ratio" (str): The compression ratio achieved, in a human-readable format.725 - "rate" (str): The compression rate achieved, in a human-readable format.726 - "saving" (str): Estimated savings in GPT-4 token usage.727 728 """729 assert len(force_tokens) <= self.max_force_token # báo hiệu force token730 token_map = {}731 for i, t in enumerate(force_tokens):732 if len(self.tokenizer.tokenize(t)) != 1:733 token_map[t] = self.added_tokens[i] # add token (là các force token) + các kí tự [NEW]734 #print('token map:', token_map)735 chunk_end_tokens = copy.deepcopy(chunk_end_tokens)736 for c in chunk_end_tokens:737 if c in token_map:738 chunk_end_tokens.append(token_map[c]) # Thêm các force token739 chunk_end_tokens = set(chunk_end_tokens)740 #print('chunk_end_tokens: ', chunk_end_tokens)741 742 if type(context) == str:743 context = [context]744 context = copy.deepcopy(context)745 746 #print('original context: ', context)747 748 if len(context) == 1 and use_context_level_filter: # Sử dụng context-level # len context > 1749 use_context_level_filter = False750 # Bắt buộc ko dùng context level751 752 n_original_token = 0753 context_chunked = []754 for i in range(len(context)):755 n_original_token += self.get_token_length(756 context[i], use_oai_tokenizer=True757 )758 for ori_token, new_token in token_map.items():759 context[i] = context[i].replace(ori_token, new_token)760 context_chunked.append(761 self.__chunk_context(context[i], chunk_end_tokens=chunk_end_tokens) # Hàm chia chunk trong llmlingua2762 ) # list chunk763 #print('context chunked:', context_chunked) (vẫn còn 5 context ban đầu)764 765 #========================================================================================766 # tinh chỉnh hyperparameter767 if use_context_level_filter: # mặc định là dùng context level trong llmlingua2 do trong hàm compress prompt ban đầu default True 768 # want use_context_level_filter but do not specify any parameters in context level?769 # Sử dụng context-level nhưng không config cụ thể các tham số trong context-level770 # we will set context_level_rate = (rate + 1.0) / 2 if specify rate or target_token * 2 if specify target_token771 if (772 target_context <= 0773 and context_level_rate >= 1.0774 and context_level_target_token <= 0775 ):776 if target_token < 0 and rate < 1.0:777 context_level_rate = (778 (rate + 1.0) / 2 if use_token_level_filter else rate779 )780 if target_token >= 0:781 context_level_target_token = (782 target_token * 2 if use_token_level_filter else target_token783 )784 785 if target_context >= 0: # Config target_context786 context_level_rate = min(target_context / len(context), 1.0)787 if context_level_target_token >= 0: # Config target_token (context_level)788 context_level_rate = min(789 context_level_target_token / n_original_token, 1.0790 )791 #========================================================================================792 context_probs, context_words = self.__get_context_prob(793 context_chunked, # list các context chunk794 token_to_word=token_to_word,795 force_tokens=force_tokens,796 token_map=token_map,797 force_reserve_digit=force_reserve_digit,798 )799 #print('context_probs: ', context_probs) # prob của tưng context800 #print('context words: ', context_words)801 #print('context level rate: ', context_level_rate)802 803 threshold = np.percentile( # filtering theo probs804 context_probs, int(100 * (1 - context_level_rate)) # chỉnh context_level_rate cho threshold, lọc context-level805 )806 #print('threshold: ', threshold)807 808 reserved_context = [] # các context được giữ lại theo threshold (từ 5 ban đầu có thể giảm đi (<5))809 context_label = [False] * len(context_probs)810 for i, p in enumerate(context_probs):811 if p >= threshold or (812 force_context_ids is not None and i in force_context_ids813 ):814 reserved_context.append(context_chunked[i])815 context_label[i] = True816 #print('reserved_context: ', reserved_context) # các context được giữ lại theo threshold817 #print('context_label: ', context_label)818 819 n_reserved_token = 0820 for chunks in reserved_context:821 for c in chunks:822 n_reserved_token += self.get_token_length(c, use_oai_tokenizer=True) # số lượng token được giữ lại823 if target_token >= 0:824 rate = min(target_token / n_reserved_token, 1.0)825 826 # có/ko sử dụng token-level vẫn trả về prompt compress 827 if use_token_level_filter: # lọc theo context-level rồi lọc theo token-level ()828 compressed_context, word_list, word_label_list = self.__compress(829 reserved_context, # compress từng context reserved được giữ lại830 reduce_rate=max(0, 1 - rate),831 token_to_word=token_to_word,832 force_tokens=force_tokens,833 token_map=token_map,834 force_reserve_digit=force_reserve_digit,835 drop_consecutive=drop_consecutive,836 )837 else:838 compressed_context, word_list, word_label_list = self.__compress(839 reserved_context,840 reduce_rate=0,841 token_to_word=token_to_word,842 force_tokens=force_tokens,843 token_map=token_map,844 force_reserve_digit=force_reserve_digit,845 drop_consecutive=drop_consecutive,846 )847 848 #print('compressed_context 1: ', compressed_context) # list # Final compressed849 #print('word_list: ', word_list)850 #print('word_label_list: ', word_label_list) # labels list của từng chunk851 852 n_compressed_token = 0853 for c in compressed_context:854 n_compressed_token += self.get_token_length(c, use_oai_tokenizer=True)855 saving = (n_original_token - n_compressed_token) * 0.06 / 1000856 ratio = (857 1 if n_compressed_token == 0 else n_original_token / n_compressed_token858 )859 res = {860 "compressed_prompt": "\n\n".join(compressed_context),861 #"compressed_prompt": " ".join(compressed_context),862 #"compressed_prompt_list": compressed_context,863 "origin_tokens": n_original_token,864 "compressed_tokens": n_compressed_token,865 "ratio": f"{ratio:.1f}x",866 "rate": f"{1 / ratio * 100:.1f}%",867 "saving": f", Saving ${saving:.1f} in GPT-4.",868 }869 #print('res: ', res)870 871 if return_word_label: # Nếu trả về label word (default=False)872 words = []873 labels = []874 j = 0875 for i in range(len(context)):876 if context_label[i]:877 words.extend(word_list[j])878 labels.extend(word_label_list[j])879 j += 1880 else:881 words.extend(context_words[i])882 labels.extend([0] * len(context_words[i]))883 word_label_lines = word_sep.join( # join theo word_sep884 [f"{word}{label_sep}{label}" for word, label in zip(words, labels)]885 )886 res["fn_labeled_original_prompt"] = word_label_lines # đánh labels từng từ887 #print('res: ', res)888 return res889 # tinh chỉnh hyperparameter 890 if target_token > 0:891 rate = min(target_token / n_original_token, 1.0)892 893 if use_token_level_filter: # Chỉ Sử dụng token-level trong llmlingua2894 compressed_context, word_list, word_label_list = self.__compress( # compress theo llmlingua2895 context_chunked,896 reduce_rate=max(0, 1 - rate),897 token_to_word=token_to_word,898 force_tokens=force_tokens,899 token_map=token_map,900 force_reserve_digit=force_reserve_digit,901 drop_consecutive=drop_consecutive, # Whether to drop tokens which are in 'force_tokens' but appears consecutively in compressed prompt.902 )903 else:904 compressed_context, word_list, word_label_list = self.__compress(905 context_chunked,906 reduce_rate=0,907 token_to_word=token_to_word,908 force_tokens=force_tokens,909 token_map=token_map,910 force_reserve_digit=force_reserve_digit,911 drop_consecutive=drop_consecutive,912 )913 # giống phần trên914 #print('compressed_context 2: ', compressed_context) # compress theo token-level915 916 n_compressed_token = 0917 for c in compressed_context:918 n_compressed_token += self.get_token_length(c, use_oai_tokenizer=True)919 saving = (n_original_token - n_compressed_token) * 0.06 / 1000920 ratio = 1 if n_compressed_token == 0 else n_original_token / n_compressed_token921 res = {922 "compressed_prompt": "\n\n".join(compressed_context), 923 #"compressed_prompt": " ".join(compressed_context), # phân tách các context bằng "\n\n"924 #"compressed_prompt_list": compressed_context,925 "origin_tokens": n_original_token,926 "compressed_tokens": n_compressed_token,927 "ratio": f"{ratio:.1f}x",928 "rate": f"{1 / ratio * 100:.1f}%",929 "saving": f", Saving ${saving:.1f} in GPT-4.",930 }931 if return_word_label:932 words = []933 labels = []934 for w_list, l_list in zip(word_list, word_label_list):935 words.extend(w_list)936 labels.extend(l_list)937 938 word_label_lines = word_sep.join(939 [f"{word}{label_sep}{label}" for word, label in zip(words, labels)]940 )941 res["fn_labeled_original_prompt"] = word_label_lines942 return res943 944 def get_token_length(945 self,946 text: str,947 add_special_tokens: bool = True,948 use_oai_tokenizer: bool = False,949 ):950 if use_oai_tokenizer:951 return len(self.oai_tokenizer.encode(text))952 else:953 return len(954 self.tokenizer(text, add_special_tokens=add_special_tokens).input_ids955 )956 957 def get_prefix_length(self, prefix: str, text: str):958 possible_prefix_token = max(self.get_token_length(prefix, False) - 3, 1)959 full_input_ids = self.tokenizer(960 prefix + text[:100], add_special_tokens=False961 ).input_ids962 for i in range(possible_prefix_token, len(full_input_ids)):963 cur_prefix = self.tokenizer.decode(full_input_ids[:i])964 if cur_prefix == prefix:965 break966 assert self.tokenizer.decode(full_input_ids[i:]) == text[:100]967 return i968 969 def get_condition_ppl(970 self,971 text: str,972 question: str,973 condition_in_question: str = "none",974 granularity: str = "sentence",975 ):976 if condition_in_question == "none":977 return self.get_ppl(text, granularity=granularity)978 elif condition_in_question == "before":979 return self.get_ppl(980 question + text,981 granularity=granularity,982 condition_mode="after",983 condition_pos_id=self.get_token_length(question) - 1,984 )985 elif condition_in_question == "after":986 return self.get_ppl(987 text + question,988 granularity=granularity,989 condition_mode="after",990 condition_pos_id=self.get_token_length(text) - 1,991 )992 993 def get_dynamic_compression_ratio(994 self,995 context: list,996 target_token: float,997 iterative_size: int,998 dynamic_ratio: list,999 start: int,1000 seg_info: List[List[tuple]] = None,1001 ):1002 def get_ratio(base: float, delta: float):1003 return max(min(1, base + delta), 0)1004 1005 context_length = [self.get_token_length(ii, False) + 2 for ii in context]1006 if start:1007 context_length = context_length[1:]1008 tau = target_token / (sum(context_length) + 1)1009 res, idx, last, last_target = [], 0, 1, []1010 while idx < len(context_length):1011 if last + context_length[idx] >= iterative_size:1012 last_target.append(1013 (iterative_size - last, get_ratio(tau, dynamic_ratio[idx]))1014 )1015 res.append(last_target)1016 last = last + context_length[idx] - iterative_size1017 if last > iterative_size:1018 k = last // iterative_size1019 res.extend(1020 [[(iterative_size, get_ratio(tau, dynamic_ratio[idx]))]] * k1021 )1022 last -= k * iterative_size1023 1024 last_target = (1025 [(last, get_ratio(tau, dynamic_ratio[idx]))] if last else []1026 )1027 else:1028 last += context_length[idx]1029 last_target.append(1030 (context_length[idx], get_ratio(tau, dynamic_ratio[idx]))1031 )1032 idx += 11033 if last_target:1034 res.append(last_target)1035 return res1036 1037 def get_structured_dynamic_compression_ratio(1038 self,1039 context: list,1040 iterative_size: int,1041 dynamic_ratio: list,1042 start: int,1043 seg_info: List[List[tuple]] = None,1044 ):1045 if start:1046 pure_context = context[1:]1047 else:1048 pure_context = context1049 global_dynamic_rate, global_dynamic_compress, segments = [], [], []1050 for context_idx, text in enumerate(pure_context):1051 text_seen = 01052 for seg_idx, (seg_len, seg_rate, seg_compress) in enumerate(1053 seg_info[context_idx]1054 ):1055 seg_text = text[text_seen : text_seen + seg_len]1056 if (1057 seg_idx == len(seg_info[context_idx]) - 11058 and context_idx != len(pure_context) - 11059 ):1060 seg_text += "\n\n"1061 segments.append(seg_text)1062 if seg_compress:1063 global_dynamic_rate.append(seg_rate)1064 else:1065 global_dynamic_rate.append(1.0)1066 global_dynamic_compress.append(seg_compress)1067 text_seen += seg_len1068 origin_text = "\n\n".join(pure_context)1069 assert len("".join(segments)) == len(origin_text)1070 assert len(segments) == len(global_dynamic_rate) == len(global_dynamic_compress)1071 1072 text_input_ids = self.tokenizer(1073 "\n\n".join(context), add_special_tokens=False1074 ).input_ids[start:]1075 assert self.tokenizer.decode(text_input_ids) == origin_text1076 dynamic_compression_ratio = self.token_segment(1077 text_input_ids,1078 iterative_size,1079 segments,1080 global_dynamic_rate,1081 global_dynamic_compress,1082 )1083 return dynamic_compression_ratio1084 1085 def token_segment(1086 self,1087 text_input_ids: List[int],1088 iterative_size: int,1089 segments: List[str],1090 global_dynamic_rate: List[float],1091 global_dynamic_compress: List[bool],1092 ):1093 decode_window = 31094 seg_idx, seg_seen, token_seen_num, last_rate = 0, 0, 0, -11095 dynamic_compression_rate, local_compresssion_rate = [], []1096 for i in range(len(text_input_ids)):1097 if i < decode_window:1098 id_pre, id_cur = text_input_ids[:i], text_input_ids[: i + 1]1099 else:1100 id_pre, id_cur = (1101 text_input_ids[i - decode_window + 1 : i],1102 text_input_ids[i - decode_window + 1 : i + 1],1103 )1104 cur_word = self.tokenizer.decode(id_cur)[1105 len(self.tokenizer.decode(id_pre)) :1106 ]1107 cur_word_len = len(cur_word)1108 if cur_word_len and cur_word_len >= len(segments[seg_idx]) - seg_seen:1109 possible_rate, possible_compress = [], []1110 while (1111 cur_word_len and cur_word_len >= len(segments[seg_idx]) - seg_seen1112 ):1113 possible_rate.append(global_dynamic_rate[seg_idx])1114 possible_compress.append(global_dynamic_compress[seg_idx])1115 cur_word_len -= len(segments[seg_idx]) - seg_seen1116 seg_idx += 11117 seg_seen = 01118 if cur_word_len:1119 possible_rate.append(global_dynamic_rate[seg_idx])1120 possible_compress.append(global_dynamic_compress[seg_idx])1121 new_rate = 1.0 if False in possible_compress else min(possible_rate)1122 else:1123 new_rate = global_dynamic_rate[seg_idx]1124 if new_rate != last_rate and i - token_seen_num:1125 local_compresssion_rate.append((i - token_seen_num, last_rate))1126 token_seen_num = i1127 last_rate = new_rate1128 seg_seen += cur_word_len1129 if (i + 1) % iterative_size == 0:1130 if token_seen_num != i + 1:1131 local_compresssion_rate.append((i + 1 - token_seen_num, last_rate))1132 token_seen_num = i + 11133 dynamic_compression_rate.append(local_compresssion_rate[:])1134 local_compresssion_rate = []1135 if token_seen_num != len(text_input_ids):1136 local_compresssion_rate.append(1137 (len(text_input_ids) - token_seen_num, last_rate)1138 )1139 if local_compresssion_rate != []:1140 dynamic_compression_rate.append(local_compresssion_rate[:])1141 return dynamic_compression_rate1142 1143 def control_context_budget(1144 self,1145 context: List[str],1146 context_tokens_length: List[int],1147 target_token: float,1148 force_context_ids: List[int] = None,1149 force_context_number: int = None,1150 question: str = "",1151 condition_in_question: str = "none",1152 reorder_context: str = "original",1153 dynamic_context_compression_ratio: float = 0.0,1154 rank_method: str = "longllmlingua",1155 context_budget: str = "+100",1156 context_segs: List[List[str]] = None,1157 context_segs_rate: List[List[float]] = None,1158 context_segs_compress: List[List[bool]] = None,1159 ):1160 demostrations_sort = self.get_rank_results(1161 context,1162 question,1163 rank_method,1164 condition_in_question,1165 context_tokens_length,1166 )1167 1168 if target_token < 0:1169 target_token = 1001170 target_token = eval("target_token" + context_budget)1171 res = []1172 used = force_context_ids if force_context_ids is not None else []1173 if context_segs is not None:1174 for idx, _ in enumerate(context):1175 if False in context_segs_compress[idx]:1176 used.append(idx)1177 1178 self.context_idxs.append([x for idx, (x, _) in enumerate(demostrations_sort)])1179 for idx, _ in demostrations_sort:1180 if idx >= len(context_tokens_length):1181 continue1182 target_token -= context_tokens_length[idx]1183 if idx not in used:1184 used.append(idx)1185 if target_token < 0 or (1186 force_context_number is not None and len(res) >= force_context_number1187 ):1188 break1189 original_used = used1190 if reorder_context == "original":1191 used = sorted(used)1192 elif reorder_context == "two_stage":1193 l, r = [_ for idx, _ in enumerate(used) if idx % 2 == 0], [1194 _ for idx, _ in enumerate(used) if idx % 2 == 11195 ]1196 used = l + r[::-1]1197 1198 if dynamic_context_compression_ratio > 0:1199 N = len(used)1200 dynamic_ratio = [