CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
beam_constraints.py534 linesDownload Raw Back to generation
1from abc import ABC, abstractmethod2from typing import Optional3 4from ..utils import logging5 6 7logger = logging.get_logger(__name__)8 9 10# TODO joao, manuel: remove in v4.58.011class Constraint(ABC):12    r"""Abstract base class for all constraints that can be applied during generation.13    It must define how the constraint can be satisfied.14 15    All classes that inherit Constraint must follow the requirement that16 17    ```py18    completed = False19    while not completed:20        _, completed = constraint.update(constraint.advance())21    ```22 23    will always terminate (halt).24    """25 26    def __init__(self):27        logger.warning_once(28            "Importing `Constraint` classes is deprecated and will be removed in v4.58.0. Constrained beam search has been moved to the Hub: https://hf.co/transformers-community/constrained-beam-search. Please import using `from transformers.generation import Constraint` instead."29        )30        # test for the above condition31        self.test()32 33    def test(self):34        """35        Tests whether this constraint has been properly defined.36        """37        counter = 038        completed = False39        while not completed:40            if counter == 1:41                self.reset()42            advance = self.advance()43            if not self.does_advance(advance):44                raise Exception(45                    "Custom Constraint is not defined correctly. self.does_advance(self.advance()) must be true."46                )47 48            stepped, completed, reset = self.update(advance)49            counter += 150 51            if counter > 10000:52                raise Exception("update() does not fulfill the constraint.")53 54        if self.remaining() != 0:55            raise Exception("Custom Constraint is not defined correctly.")56 57    @abstractmethod58    def advance(self):59        """60        When called, returns the token(s) that would take this constraint one step closer to being fulfilled.61 62        Return:63            token_ids (Union[int, list[int], None]):64                - A single token ID (int) that advances the constraint, or65                - A list of token IDs that could advance the constraint66                - None if the constraint is completed or cannot be advanced67        """68        raise NotImplementedError(69            f"{self.__class__} is an abstract class. Only classes inheriting this class can be called."70        )71 72    @abstractmethod73    def does_advance(self, token_id: int):74        """75        Reads in a token and returns whether it creates progress.76        """77        raise NotImplementedError(78            f"{self.__class__} is an abstract class. Only classes inheriting this class can be called."79        )80 81    @abstractmethod82    def update(self, token_id: int):83        """84        Reads in a token and returns booleans that indicate the progress made by it. This function will update the85        state of this object unlikes `does_advance(self, token_id: int)`.86 87        This isn't to test whether a certain token will advance the progress; it's to update its state as if it has88        been generated. This becomes important if token_id != desired token (refer to else statement in89        PhrasalConstraint)90 91        Args:92            token_id(`int`):93                The id of a newly generated token in the beam search.94        Return:95            stepped(`bool`):96                Whether this constraint has become one step closer to being fulfuilled.97            completed(`bool`):98                Whether this constraint has been completely fulfilled by this token being generated.99            reset (`bool`):100                Whether this constraint has reset its progress by this token being generated.101        """102        raise NotImplementedError(103            f"{self.__class__} is an abstract class. Only classes inheriting this class can be called."104        )105 106    @abstractmethod107    def reset(self):108        """109        Resets the state of this constraint to its initialization. We would call this in cases where the fulfillment of110        a constraint is abrupted by an unwanted token.111        """112        raise NotImplementedError(113            f"{self.__class__} is an abstract class. Only classes inheriting this class can be called."114        )115 116    @abstractmethod117    def remaining(self):118        """119        Returns the number of remaining steps of `advance()` in order to complete this constraint.120        """121        raise NotImplementedError(122            f"{self.__class__} is an abstract class. Only classes inheriting this class can be called."123        )124 125    @abstractmethod126    def copy(self, stateful=False):127        """128        Creates a new instance of this constraint.129 130        Args:131            stateful(`bool`): Whether to not only copy the constraint for new instance, but also its state.132 133        Return:134            constraint(`Constraint`): The same constraint as the one being called from.135        """136        raise NotImplementedError(137            f"{self.__class__} is an abstract class. Only classes inheriting this class can be called."138        )139 140 141class PhrasalConstraint(Constraint):142    r"""143    [`Constraint`] enforcing that an ordered sequence of tokens is included in the output.144 145    Args:146        token_ids (`list[int]`):147            The id of the token that must be generated by the output.148    """149 150    def __init__(self, token_ids: list[int]):151        super(Constraint, self).__init__()152 153        if not isinstance(token_ids, list) or len(token_ids) == 0:154            raise ValueError(f"`token_ids` has to be a non-empty list, but is {token_ids}.")155        if any((not isinstance(token_id, int) or token_id < 0) for token_id in token_ids):156            raise ValueError(f"Each list in `token_ids` has to be a list of positive integers, but is {token_ids}.")157 158        self.token_ids = token_ids159 160        self.seqlen = len(self.token_ids)161        self.fulfilled_idx = -1  # the index of the currently fulfilled step162        self.completed = False163 164    def advance(self):165        if self.completed:166            return None167        return self.token_ids[self.fulfilled_idx + 1]168 169    def does_advance(self, token_id: int):170        if not isinstance(token_id, int):171            raise TypeError(f"`token_id` has to be an `int`, but is {token_id} of type {type(token_id)}")172 173        if self.completed:174            return False175 176        return token_id == self.token_ids[self.fulfilled_idx + 1]177 178    def update(self, token_id: int):179        if not isinstance(token_id, int):180            raise TypeError(f"`token_id` has to be an `int`, but is {token_id} of type {type(token_id)}")181 182        stepped = False183        completed = False184        reset = False185 186        if self.does_advance(token_id):187            self.fulfilled_idx += 1188            stepped = True189            if self.fulfilled_idx == (self.seqlen - 1):190                completed = True191            self.completed = completed192        else:193            # failed to make progress.194            reset = True195            self.reset()196        return stepped, completed, reset197 198    def reset(self):199        self.completed = False200        self.fulfilled_idx = 0201 202    def remaining(self):203        return self.seqlen - (self.fulfilled_idx + 1)204 205    def copy(self, stateful=False):206        new_constraint = PhrasalConstraint(self.token_ids)207 208        if stateful:209            new_constraint.seq_len = self.seqlen210            new_constraint.fulfilled_idx = self.fulfilled_idx211            new_constraint.completed = self.completed212 213        return new_constraint214 215 216class DisjunctiveTrie:217    def __init__(self, nested_token_ids: list[list[int]], no_subsets=True):218        r"""219        A helper class that builds a trie with the words represented in `nested_token_ids`.220        """221        self.max_height = max([len(one) for one in nested_token_ids])222 223        root = {}224        for token_ids in nested_token_ids:225            level = root226            for tidx, token_id in enumerate(token_ids):227                if token_id not in level:228                    level[token_id] = {}229 230                level = level[token_id]231 232        if no_subsets and self.has_subsets(root, nested_token_ids):233            raise ValueError(234                "Each list in `nested_token_ids` can't be a complete subset of another list, but is"235                f" {nested_token_ids}."236            )237 238        self.trie = root239 240    def next_tokens(self, current_seq):241        """242        The next possible tokens that will progress the trie, given the current sequence of tokens in `current_seq`.243        """244        start = self.trie245 246        for current_token in current_seq:247            start = start[current_token]248 249        next_tokens = list(start.keys())250 251        return next_tokens252 253    def reached_leaf(self, current_seq):254        next_tokens = self.next_tokens(current_seq)255 256        return len(next_tokens) == 0257 258    def count_leaves(self, root):259        next_nodes = list(root.values())260        if len(next_nodes) == 0:261            return 1262        else:263            return sum([self.count_leaves(nn) for nn in next_nodes])264 265    def has_subsets(self, trie, nested_token_ids):266        """267        Returns whether # of leaves == # of words. Otherwise some word is a subset of another.268        """269        leaf_count = self.count_leaves(trie)270        return len(nested_token_ids) != leaf_count271 272 273class DisjunctiveConstraint(Constraint):274    r"""275    A special [`Constraint`] that is fulfilled by fulfilling just one of several constraints.276 277    Args:278        nested_token_ids (`list[list[int]]`):279            A list of words, where each word is a list of ids. This constraint is fulfilled by generating just one from280            the list of words.281    """282 283    def __init__(self, nested_token_ids: list[list[int]]):284        super(Constraint, self).__init__()285 286        if not isinstance(nested_token_ids, list) or len(nested_token_ids) == 0:287            raise ValueError(f"`nested_token_ids` has to be a non-empty list, but is {nested_token_ids}.")288        if any(not isinstance(token_ids, list) for token_ids in nested_token_ids):289            raise ValueError(f"`nested_token_ids` has to be a list of lists, but is {nested_token_ids}.")290        if any(291            any((not isinstance(token_id, int) or token_id < 0) for token_id in token_ids)292            for token_ids in nested_token_ids293        ):294            raise ValueError(295                f"Each list in `nested_token_ids` has to be a list of positive integers, but is {nested_token_ids}."296            )297 298        self.trie = DisjunctiveTrie(nested_token_ids)299        self.token_ids = nested_token_ids300 301        self.seqlen = self.trie.max_height302        self.current_seq = []303        self.completed = False304 305    def advance(self):306        token_list = self.trie.next_tokens(self.current_seq)307 308        if len(token_list) == 0:309            return None310        else:311            return token_list312 313    def does_advance(self, token_id: int):314        if not isinstance(token_id, int):315            raise TypeError(f"`token_id` is supposed to be type `int`, but is {token_id} of type {type(token_id)}")316 317        next_tokens = self.trie.next_tokens(self.current_seq)318 319        return token_id in next_tokens320 321    def update(self, token_id: int):322        if not isinstance(token_id, int):323            raise TypeError(f"`token_id` is supposed to be type `int`, but is {token_id} of type {type(token_id)}")324 325        stepped = False326        completed = False327        reset = False328 329        if self.does_advance(token_id):330            self.current_seq.append(token_id)331            stepped = True332        else:333            reset = True334            self.reset()335 336        completed = self.trie.reached_leaf(self.current_seq)337        self.completed = completed338 339        return stepped, completed, reset340 341    def reset(self):342        self.completed = False343        self.current_seq = []344 345    def remaining(self):346        if self.completed:347            # since this can be completed without reaching max height348            return 0349        else:350            return self.seqlen - len(self.current_seq)351 352    def copy(self, stateful=False):353        new_constraint = DisjunctiveConstraint(self.token_ids)354 355        if stateful:356            new_constraint.seq_len = self.seqlen357            new_constraint.current_seq = self.current_seq358            new_constraint.completed = self.completed359 360        return new_constraint361 362 363class ConstraintListState:364    r"""365    A class for beam scorers to track its progress through a list of constraints.366 367    Args:368        constraints (`list[Constraint]`):369            A list of [`Constraint`] objects that must be fulfilled by the beam scorer.370    """371 372    def __init__(self, constraints: list[Constraint]):373        self.constraints = constraints374 375        # max # of steps required to fulfill a given constraint376        self.max_seqlen = max([c.seqlen for c in constraints])377        self.n_constraints = len(constraints)378        self.completed = False379 380        self.init_state()381 382    def init_state(self):383        self.complete_constraints = []384        self.inprogress_constraint = None385        self.pending_constraints = [constraint.copy(stateful=False) for constraint in self.constraints]386 387    def get_bank(self):388        add = 0389        if self.inprogress_constraint:390            # extra points for having a constraint mid-fulfilled391            add += self.max_seqlen - self.inprogress_constraint.remaining()392 393        return (len(self.complete_constraints) * self.max_seqlen) + add394 395    def advance(self):396        """The list of tokens to generate such that we can make progress.397        By "list" we don't mean the list of token that will fully fulfill a constraint.398 399        Given constraints `c_i = {t_ij | j == # of tokens}`, If we're not in the middle of progressing through a400        specific constraint `c_i`, we return:401 402        `[t_k1 for k in indices of unfulfilled constraints]`403 404        If we are in the middle of a constraint, then we return:405            `[t_ij]`, where `i` is the index of the inprogress constraint, `j` is the next step for the constraint.406 407        Though we don't care which constraint is fulfilled first, if we are in the progress of fulfilling a constraint,408        that's the only one we'll return.409        """410        token_list = []411        if self.inprogress_constraint is None:412            for constraint in self.pending_constraints:  # "pending" == "unfulfilled yet"413                advance = constraint.advance()414                if isinstance(advance, int):415                    token_list.append(advance)416                elif isinstance(advance, list):417                    token_list.extend(advance)418        else:419            advance = self.inprogress_constraint.advance()420            if isinstance(advance, int):421                token_list.append(advance)422            elif isinstance(advance, list):423                token_list.extend(advance)424 425        if len(token_list) == 0:426            return None427        else:428            return token_list429 430    def reset(self, token_ids: Optional[list[int]]):431        """432        token_ids: the tokens generated thus far to reset the state of the progress through constraints.433        """434        self.init_state()435 436        if token_ids is not None:437            for token in token_ids:438                # completes or steps **one** constraint439                complete, stepped = self.add(token)440 441                # the entire list of constraints are fulfilled442                if self.completed:443                    break444 445    def add(self, token_id: int):446        if not isinstance(token_id, int):447            raise TypeError(f"`token_id` should be an `int`, but is `{token_id}`.")448 449        complete, stepped = False, False450 451        if self.completed:452            complete = True453            stepped = False454            return complete, stepped455 456        if self.inprogress_constraint is not None:457            # In the middle of fulfilling a constraint. If the `token_id` *does* makes an incremental progress to current458            # job, simply update the state459 460            stepped, complete, reset = self.inprogress_constraint.update(token_id)461            if reset:462                # 1. If the next token breaks the progress, then we must restart.463                #     e.g. constraint = "I love pies" and sequence so far is "I love" but `token_id` == "books".464 465                #     But that doesn't mean we self.init_state(), since we only reset the state for this particular466                #     constraint, not the full list of constraints.467 468                self.pending_constraints.append(self.inprogress_constraint.copy(stateful=False))469                self.inprogress_constraint = None470 471            if complete:472                # 2. If the next token completes the constraint, move it to completed list, set473                #     inprogress to None. If there are no pending constraints either, then this full list of constraints474                #     is complete.475 476                self.complete_constraints.append(self.inprogress_constraint)477                self.inprogress_constraint = None478 479                if len(self.pending_constraints) == 0:480                    # we're done!481                    self.completed = True482 483        else:484            # Not in the middle of fulfilling a constraint. So does this `token_id` helps us step towards any of our list485            # of constraints?486 487            for cidx, pending_constraint in enumerate(self.pending_constraints):488                if pending_constraint.does_advance(token_id):489                    stepped, complete, reset = pending_constraint.update(token_id)490 491                    if not stepped:492                        raise Exception(493                            "`constraint.update(token_id)` is not yielding incremental progress, "494                            "even though `constraint.does_advance(token_id)` is true."495                        )496 497                    if complete:498                        self.complete_constraints.append(pending_constraint)499                        self.inprogress_constraint = None500 501                    if not complete and stepped:502                        self.inprogress_constraint = pending_constraint503 504                    if complete or stepped:505                        # If we made any progress at all, then it's at least not a "pending constraint".506 507                        self.pending_constraints = (508                            self.pending_constraints[:cidx] + self.pending_constraints[cidx + 1 :]509                        )510 511                        if len(self.pending_constraints) == 0 and self.inprogress_constraint is None:512                            # If there's no longer any pending after this and no inprogress either, then we must be513                            # complete.514 515                            self.completed = True516 517                        break  # prevent accidentally stepping through multiple constraints with just one token.518 519        return complete, stepped520 521    def copy(self, stateful=True):522        new_state = ConstraintListState(self.constraints)  # we actually never though self.constraints objects523        # throughout this process. So it's at initialization state.524 525        if stateful:526            new_state.complete_constraints = [527                constraint.copy(stateful=True) for constraint in self.complete_constraints528            ]529            if self.inprogress_constraint is not None:530                new_state.inprogress_constraint = self.inprogress_constraint.copy(stateful=True)531            new_state.pending_constraints = [constraint.copy() for constraint in self.pending_constraints]532 533        return new_state534 
Aluode/PerceptionLabPortable · CoolFace