CoolFace
Apppublic

geraskalnas/document-summarization

sourceHugging Faceapache-2.0updated 3y agoView on Hugging Face
1likes
aggregate.py242 linesDownload Raw Back to root
1"""2aggregate.py - module for aggregating text from multiple sources/multiple parts of a single source.3    Primary usage is through the BatchAggregator class.4 5How it works:61. We tell the language model to do it.72. The language model does it.83. Yaay!9"""10import logging11import pprint as pp12import time13 14import torch15from transformers import GenerationConfig, pipeline16 17from utils import compare_model_size18 19# Setting up logging20logging.basicConfig(21    level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"22)23 24 25class BatchAggregator:26    """27    BatchAggregator is a class for aggregating text from multiple sources.28 29    Usage:30    >>> from aggregate import BatchAggregator31    >>> aggregator = BatchAggregator()32    >>> agg = aggregator.infer_aggregate(["This is a test", "This is another test"])33    >>> print(agg)34    """35 36    GENERIC_CONFIG = GenerationConfig(37        num_beams=8,38        early_stopping=True,39        do_sample=False,40        min_new_tokens=32,41        max_new_tokens=256,42        repetition_penalty=1.1,43        length_penalty=1.4,44        no_repeat_ngram_size=4,45        encoder_no_repeat_ngram_size=5,46    )47    CONFIGURED_MODELS = [48        "pszemraj/bart-large-mnli-dolly_hhrlhf-v1",49        "pszemraj/bart-base-instruct-dolly_hhrlhf",50        "pszemraj/flan-t5-large-instruct-dolly_hhrlhf",51        "pszemraj/flan-t5-base-instruct-dolly_hhrlhf",52    ]  # these have generation configs defined for this task in their model repos53 54    DEFAULT_INSTRUCTION = "Write a comprehensive yet concise summary that pulls together the main points of the following text:"55 56    def __init__(57        self,58        model_name: str = "pszemraj/bart-large-mnli-dolly_hhrlhf-v1",59        force_cpu: bool = False,60        **kwargs,61    ):62        """63        __init__ initializes the BatchAggregator class.64 65        :param str model_name: model name to use, default: "pszemraj/bart-large-mnli-dolly_hhrlhf-v1"66        :param bool force_cpu: force the model to run on CPU, default: False67        """68        self.device = None69        self.is_compiled = False70        self.model_name = None71        self.aggregator = None72        self.force_cpu = force_cpu73        self.logger = logging.getLogger(__name__)74        self.init_model(model_name)75 76    def init_model(self, model_name: str) -> None:77        """78        Initialize the model.79 80        :param model_name: The name of the model to use.81        """82        # Free up memory83        if torch.cuda.is_available():84            torch.cuda.empty_cache()85 86        self.logger.info(f"Setting model to {model_name}")87        self.model_name = model_name88        self.aggregator = self._create_pipeline(model_name)89        self._configure_model()90        # update the generation config with the specific tokenizer91        tokenizer_params = {92            "decoder_start_token_id": 093            if "t5" in model_name.lower()94            else self.aggregator.tokenizer.eos_token_id,95            "eos_token_id": 196            if "t5" in model_name.lower()97            else self.aggregator.tokenizer.eos_token_id,98            "pad_token_id": 099            if "t5" in model_name.lower()100            else self.aggregator.tokenizer.pad_token_id,101        }102        self.update_generation_config(**tokenizer_params)103 104    def _create_pipeline(105        self, model_name: str = "pszemraj/bart-large-mnli-dolly_hhrlhf-v1"106    ) -> pipeline:107        """108        _create_pipeline creates a pipeline for the model.109 110        :param str model_name: model name to use, default: "pszemraj/bart-large-mnli-dolly_hhrlhf-v1"111        :return pipeline: the pipeline for the model112 113        :raises Exception: if the pipeline cannot be created114        """115        self.device = 0 if torch.cuda.is_available() and not self.force_cpu else -1116        try:117            self.logger.info(118                f"Creating pipeline with model {model_name} on device {self.device}"119            )120            return pipeline(121                "text2text-generation",122                model_name,123                device=self.device,124                torch_dtype=torch.float32,125            )126        except Exception as e:127            self.logger.error(f"Failed to create pipeline: {e}")128            raise129 130    def _configure_model(self):131        """132        Configure the model for generation.133        """134        try:135            self.aggregator.model = torch.compile(self.aggregator.model)136            self.is_compiled = True137        except Exception as e:138            self.logger.warning(f"Could not compile model with Torch 2.0: {e}")139 140        if self.model_name not in self.CONFIGURED_MODELS:141            self.logger.info("Setting generation config to general defaults")142            self._set_default_generation_config()143        else:144            try:145                self.logger.info("Loading generation config from hub")146                self.aggregator.model.generation_config = (147                    GenerationConfig.from_pretrained(self.model_name)148                )149            except Exception as e:150                self.logger.warning(151                    f"Could not load generation config, using defaults: {e}"152                )153                self._set_default_generation_config()154 155        self.logger.info(self.aggregator.model.generation_config.to_json_string())156 157    def _set_default_generation_config(self):158        """159        Set the default generation configuration for the model.160        """161        self.aggregator.model.generation_config = self.GENERIC_CONFIG162 163        if (164            "large"165            or "xl" in self.model_name.lower()166            or compare_model_size(self.model_name, 500)167        ):168            upd = {"num_beams": 4}169            self.update_generation_config(**upd)170 171    def update_generation_config(self, **kwargs):172        """173        Update the generation configuration with the specified parameters.174 175        Args:176            **kwargs: The parameters to update in the generation configuration.177        """178        self.logger.info(f"Updating generation config with {pp.pformat(kwargs)}")179 180        self.aggregator.model.generation_config.update(**kwargs)181 182    def get_generation_config(self) -> dict:183        """184        Get the current generation configuration.185 186        Returns:187            dict: The current generation configuration.188        """189        return self.aggregator.model.generation_config.to_dict()190 191    def update_loglevel(self, level: str = "INFO"):192        """193        Update the log level.194 195        Args:196            level (str): The log level to set. Defaults to "INFO".197        """198        self.logger.setLevel(level)199 200    def infer_aggregate(201        self,202        text_list: list,203        instruction: str = DEFAULT_INSTRUCTION,204        **kwargs,205    ) -> str:206        f"""207        infer_aggregate - infers a consolidated summary from a list of texts.208 209        Args:210            text_list (list): The texts to summarize.211            instruction (str): The instruction for the summary. Defaults to {self.DEFAULT_INSTRUCTION}.212            **kwargs: Additional parameters to update in the generation configuration.213 214        Returns:215            The generated summary.216        """217        joined_text = "\n".join(text_list)218        prompt = f"{instruction}\n\n{joined_text}\n"219        if kwargs:220            self.update_generation_config(**kwargs)221        st = time.perf_counter()222        self.logger.info(f"inference on {len(text_list)} texts ...")223        result = self.aggregator(224            prompt,225            generation_config=self.aggregator.model.generation_config,226        )[0]["generated_text"]227        self.logger.info(f"Done. runtime:\t{round(time.perf_counter() - st, 2)}s")228        self.logger.info(229            f"Input tokens:\t{self.count_tokens(prompt)}. Output tokens:\t{self.count_tokens(result)}"230        )231        self.logger.debug(f"Generated text:\n{result}")232 233        return result234 235    def count_tokens(self, text: str) -> int:236        """count the number of tokens in a text"""237        return (238            len(self.aggregator.tokenizer.encode(text, truncation=False, padding=False))239            if text240            else 0241        )242