CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
flax_logits_process.py545 linesDownload Raw Back to generation
1# coding=utf-82# Copyright 2021 The HuggingFace Inc. team3#4# Licensed under the Apache License, Version 2.0 (the "License");5# you may not use this file except in compliance with the License.6# You may obtain a copy of the License at7#8#     http://www.apache.org/licenses/LICENSE-2.09#10# Unless required by applicable law or agreed to in writing, software11# distributed under the License is distributed on an "AS IS" BASIS,12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13# See the License for the specific language governing permissions and14# limitations under the License.15 16import inspect17 18import jax19import jax.lax as lax20import jax.numpy as jnp21from jax.experimental import sparse22 23from ..utils import add_start_docstrings24from ..utils.logging import get_logger25 26 27logger = get_logger(__name__)28 29 30LOGITS_PROCESSOR_INPUTS_DOCSTRING = r"""31    Args:32        input_ids (`jnp.ndarray` of shape `(batch_size, sequence_length)`):33            Indices of input sequence tokens in the vocabulary.34 35            Indices can be obtained using [`PreTrainedTokenizer`]. See [`PreTrainedTokenizer.encode`] and36            [`PreTrainedTokenizer.__call__`] for details.37 38            [What are input IDs?](../glossary#input-ids)39        scores (`jnp.ndarray` of shape `(batch_size, config.vocab_size)`):40            Prediction scores of a language modeling head. These can be logits for each vocabulary when not using beam41            search or log softmax for each vocabulary token when using beam search42        kwargs (`dict[str, Any]`, *optional*):43            Additional logits processor specific kwargs.44 45    Return:46        `jnp.ndarray` of shape `(batch_size, config.vocab_size)`: The processed prediction scores.47 48"""49 50 51class FlaxLogitsProcessor:52    """Abstract base class for all logit processors that can be applied during generation."""53 54    @add_start_docstrings(LOGITS_PROCESSOR_INPUTS_DOCSTRING)55    def __call__(self, input_ids: jnp.ndarray, scores: jnp.ndarray) -> jnp.ndarray:56        """Flax method for processing logits."""57        raise NotImplementedError(58            f"{self.__class__} is an abstract class. Only classes inheriting this class can be called."59        )60 61 62class FlaxLogitsWarper:63    """Abstract base class for all logit warpers that can be applied during generation with multinomial sampling."""64 65    @add_start_docstrings(LOGITS_PROCESSOR_INPUTS_DOCSTRING)66    def __call__(self, input_ids: jnp.ndarray, scores: jnp.ndarray) -> jnp.ndarray:67        """Flax method for warping logits."""68        raise NotImplementedError(69            f"{self.__class__} is an abstract class. Only classes inheriting this class can be called."70        )71 72 73class FlaxLogitsProcessorList(list):74    """75    This class can be used to create a list of [`FlaxLogitsProcessor`] or [`FlaxLogitsWarper`] to subsequently process76    a `scores` input tensor. This class inherits from list and adds a specific *__call__* method to apply each77    [`FlaxLogitsProcessor`] or [`FlaxLogitsWarper`] to the inputs.78    """79 80    @add_start_docstrings(LOGITS_PROCESSOR_INPUTS_DOCSTRING)81    def __call__(self, input_ids: jnp.ndarray, scores: jnp.ndarray, cur_len: int, **kwargs) -> jnp.ndarray:82        for processor in self:83            function_args = inspect.signature(processor.__call__).parameters84            if len(function_args) > 3:85                if not all(arg in kwargs for arg in list(function_args.keys())[2:]):86                    raise ValueError(87                        f"Make sure that all the required parameters: {list(function_args.keys())} for "88                        f"{processor.__class__} are passed to the logits processor."89                    )90                scores = processor(input_ids, scores, cur_len, **kwargs)91            else:92                scores = processor(input_ids, scores, cur_len)93        return scores94 95 96class FlaxTemperatureLogitsWarper(FlaxLogitsWarper):97    r"""98    [`FlaxLogitsWarper`] for temperature (exponential scaling output probability distribution).99 100    Args:101        temperature (`float`):102            The value used to module the logits distribution.103    """104 105    def __init__(self, temperature: float):106        if not isinstance(temperature, float) or not (temperature > 0):107            raise ValueError(f"`temperature` has to be a strictly positive float, but is {temperature}")108 109        self.temperature = temperature110 111    def __call__(self, input_ids: jnp.ndarray, scores: jnp.ndarray, cur_len: int) -> jnp.ndarray:112        scores = scores / self.temperature113        return scores114 115 116class FlaxTopPLogitsWarper(FlaxLogitsWarper):117    """118    [`FlaxLogitsWarper`] that performs top-p, i.e. restricting to top tokens summing to prob_cut_off <= prob_cut_off.119 120    Args:121        top_p (`float`):122            If set to < 1, only the smallest set of most probable tokens with probabilities that add up to `top_p` or123            higher are kept for generation.124        filter_value (`float`, *optional*, defaults to -inf):125            All filtered values will be set to this float value.126        min_tokens_to_keep (`int`, *optional*, defaults to 1):127            Minimum number of tokens that cannot be filtered.128    """129 130    def __init__(self, top_p: float, filter_value: float = -float("Inf"), min_tokens_to_keep: int = 1):131        if not isinstance(top_p, float) or (top_p < 0 or top_p > 1.0):132            raise ValueError(f"`top_p` has to be a float > 0 and < 1, but is {top_p}")133        if not isinstance(min_tokens_to_keep, int) or (min_tokens_to_keep < 1):134            raise ValueError(f"`min_tokens_to_keep` has to be a positive integer, but is {min_tokens_to_keep}")135 136        self.top_p = top_p137        self.filter_value = filter_value138        self.min_tokens_to_keep = min_tokens_to_keep139 140    def __call__(self, input_ids: jnp.ndarray, scores: jnp.ndarray, cur_len: int) -> jnp.ndarray:141        topk_scores, topk_indices = lax.top_k(scores, scores.shape[-1])142 143        mask_scores = jnp.full_like(scores, self.filter_value)144        cumulative_probs = jax.nn.softmax(topk_scores, axis=-1).cumsum(axis=-1)145        score_mask = cumulative_probs < self.top_p146 147        # include the token that is higher than top_p as well148        score_mask = jnp.roll(score_mask, 1)149        score_mask |= score_mask.at[:, 0].set(True)150 151        # min tokens to keep152        score_mask = score_mask.at[:, : self.min_tokens_to_keep].set(True)153 154        topk_next_scores = jnp.where(score_mask, topk_scores, mask_scores)155        next_scores = jax.lax.sort_key_val(topk_indices, topk_next_scores)[-1]156 157        return next_scores158 159 160class FlaxTopKLogitsWarper(FlaxLogitsWarper):161    r"""162    [`FlaxLogitsWarper`] that performs top-k, i.e. restricting to the k highest probability elements.163 164    Args:165        top_k (`int`):166            The number of highest probability vocabulary tokens to keep for top-k-filtering.167        filter_value (`float`, *optional*, defaults to -inf):168            All filtered values will be set to this float value.169        min_tokens_to_keep (`int`, *optional*, defaults to 1):170            Minimum number of tokens that cannot be filtered.171    """172 173    def __init__(self, top_k: int, filter_value: float = -float("Inf"), min_tokens_to_keep: int = 1):174        if not isinstance(top_k, int) or top_k <= 0:175            raise ValueError(f"`top_k` has to be a strictly positive integer, but is {top_k}")176 177        self.top_k = max(top_k, min_tokens_to_keep)178        self.filter_value = filter_value179 180    def __call__(self, input_ids: jnp.ndarray, scores: jnp.ndarray, cur_len: int) -> jnp.ndarray:181        batch_size, vocab_size = scores.shape182        next_scores_flat = jnp.full(batch_size * vocab_size, self.filter_value)183 184        topk = min(self.top_k, scores.shape[-1])  # Safety check185        topk_scores, topk_indices = lax.top_k(scores, topk)186        shift = jnp.broadcast_to((jnp.arange(batch_size) * vocab_size)[:, None], (batch_size, topk)).flatten()187        topk_scores_flat = topk_scores.flatten()188        topk_indices_flat = topk_indices.flatten() + shift189 190        next_scores_flat = next_scores_flat.at[topk_indices_flat].set(topk_scores_flat)191        next_scores = next_scores_flat.reshape(batch_size, vocab_size)192        return next_scores193 194 195class FlaxForcedBOSTokenLogitsProcessor(FlaxLogitsProcessor):196    r"""197    [`FlaxLogitsProcessor`] that enforces the specified token as the first generated token.198 199    Args:200        bos_token_id (`int`):201            The id of the token to force as the first generated token.202    """203 204    def __init__(self, bos_token_id: int):205        self.bos_token_id = bos_token_id206 207    def __call__(self, input_ids: jnp.ndarray, scores: jnp.ndarray, cur_len: int) -> jnp.ndarray:208        new_scores = jnp.full(scores.shape, -float("inf"))209 210        apply_penalty = 1 - jnp.bool_(cur_len - 1)211 212        scores = jnp.where(apply_penalty, new_scores.at[:, self.bos_token_id].set(0), scores)213 214        return scores215 216 217class FlaxForcedEOSTokenLogitsProcessor(FlaxLogitsProcessor):218    r"""219    [`FlaxLogitsProcessor`] that enforces the specified token as the last generated token when `max_length` is reached.220 221    Args:222        max_length (`int`):223            The maximum length of the sequence to be generated.224        eos_token_id (`int`):225            The id of the token to force as the last generated token when `max_length` is reached.226    """227 228    def __init__(self, max_length: int, eos_token_id: int):229        self.max_length = max_length230        self.eos_token_id = eos_token_id231 232    def __call__(self, input_ids: jnp.ndarray, scores: jnp.ndarray, cur_len: int) -> jnp.ndarray:233        new_scores = jnp.full(scores.shape, -float("inf"))234 235        apply_penalty = 1 - jnp.bool_(cur_len - self.max_length + 1)236 237        scores = jnp.where(apply_penalty, new_scores.at[:, self.eos_token_id].set(0), scores)238 239        return scores240 241 242class FlaxMinLengthLogitsProcessor(FlaxLogitsProcessor):243    r"""244    [`FlaxLogitsProcessor`] enforcing a min-length by setting EOS probability to 0.245 246    Args:247        min_length (`int`):248            The minimum length below which the score of `eos_token_id` is set to `-float("Inf")`.249        eos_token_id (`int`):250            The id of the *end-of-sequence* token.251    """252 253    def __init__(self, min_length: int, eos_token_id: int):254        if not isinstance(min_length, int) or min_length < 0:255            raise ValueError(f"`min_length` has to be a positive integer, but is {min_length}")256 257        if not isinstance(eos_token_id, int) or eos_token_id < 0:258            raise ValueError(f"`eos_token_id` has to be a positive integer, but is {eos_token_id}")259 260        self.min_length = min_length261        self.eos_token_id = eos_token_id262 263    def __call__(self, input_ids: jnp.ndarray, scores: jnp.ndarray, cur_len: int) -> jnp.ndarray:264        # create boolean flag to decide if min length penalty should be applied265        apply_penalty = 1 - jnp.clip(cur_len - self.min_length, 0, 1)266 267        scores = jnp.where(apply_penalty, scores.at[:, self.eos_token_id].set(-float("inf")), scores)268 269        return scores270 271 272class FlaxSuppressTokensAtBeginLogitsProcessor(FlaxLogitsProcessor):273    r"""274    [`FlaxLogitsProcessor`] suppressing a list of tokens as soon as the `generate` function starts generating using275    `begin_index` tokens. This should ensure that the tokens defined by `begin_suppress_tokens` are not sampled at the276    beginning of the generation.277 278    Args:279        begin_suppress_tokens (`list[int]`):280            Tokens to not sample.281        begin_index (`int`):282            Index where the tokens are suppressed.283    """284 285    def __init__(self, begin_suppress_tokens, begin_index):286        self.begin_suppress_tokens = list(begin_suppress_tokens)287        self.begin_index = begin_index288 289    def __call__(self, input_ids, scores, cur_len: int):290        apply_penalty = 1 - jnp.bool_(cur_len - self.begin_index)291 292        scores = jnp.where(apply_penalty, scores.at[:, self.begin_suppress_tokens].set(-float("inf")), scores)293 294        return scores295 296 297class FlaxSuppressTokensLogitsProcessor(FlaxLogitsProcessor):298    r"""299    [`FlaxLogitsProcessor`] suppressing a list of tokens at each decoding step. The processor will set their log probs300    to be `-inf` so they are not sampled.301 302    Args:303        suppress_tokens (`list`):304            Tokens to not sample.305    """306 307    def __init__(self, suppress_tokens: list):308        self.suppress_tokens = list(suppress_tokens)309 310    def __call__(self, input_ids: jnp.ndarray, scores: jnp.ndarray, cur_len: int) -> jnp.ndarray:311        scores = scores.at[..., self.suppress_tokens].set(-float("inf"))312 313        return scores314 315 316class FlaxForceTokensLogitsProcessor(FlaxLogitsProcessor):317    r"""318    [`FlaxLogitsProcessor`] that takes a list of pairs of integers which indicates a mapping from generation indices to319    token indices that will be forced before sampling. The processor will set their log probs to 0 and all other tokens320    to `-inf` so that they are sampled at their corresponding index.321 322    Args:323        force_token_map (`list`):324            Map giving token ids and indices where they will be forced to be sampled.325    """326 327    def __init__(self, force_token_map):328        force_token_map = dict(force_token_map)329        # Converts the dictionary of format {index: token} containing the tokens to be forced to an array, where the330        # index of the array corresponds to the index of the token to be forced, for XLA compatibility.331        # Indexes without forced tokens will have a negative value.332        force_token_array = jnp.ones((max(force_token_map.keys()) + 1), dtype=jnp.int32) * -1333        for index, token in force_token_map.items():334            if token is not None:335                force_token_array = force_token_array.at[index].set(token)336        self.force_token_array = jnp.int32(force_token_array)337 338    def __call__(self, input_ids: jnp.ndarray, scores: jnp.ndarray, cur_len: int) -> jnp.ndarray:339        def _force_token(generation_idx):340            batch_size = scores.shape[0]341            current_token = self.force_token_array[generation_idx]342 343            new_scores = jnp.ones_like(scores, dtype=scores.dtype) * -float("inf")344            updates = jnp.zeros((batch_size, 1), dtype=scores.dtype)345            new_scores = lax.dynamic_update_slice(new_scores, updates, (0, current_token))346            return new_scores347 348        scores = lax.cond(349            cur_len >= self.force_token_array.shape[0],350            # If the current length is geq than the length of force_token_array, the processor does nothing.351            lambda: scores,352            # Otherwise, it may force a certain token.353            lambda: lax.cond(354                self.force_token_array[cur_len] >= 0,355                # Only valid (positive) tokens are forced356                lambda: _force_token(cur_len),357                # Otherwise, the processor does nothing.358                lambda: scores,359            ),360        )361        return scores362 363 364class FlaxWhisperTimeStampLogitsProcessor(FlaxLogitsProcessor):365    r"""366    Whisper specific Processor. This processor can be used to force a list of tokens. The processor will set their log367    probs to `inf` so that they are sampled at their corresponding index.368 369    Args:370        generate_config (`GenerateConfig`):371            The generate config used to generate the output. The following parameters are required:372                eos_token_id (`int`, *optional*, defaults to 50257):373                    The id of the *end-of-sequence* token.374                no_timestamps_token_id (`int`, *optional*, defaults to 50363):375                    The id of the `"<|notimestamps|>"` token.376                max_initial_timestamp_index (`int`, *optional*, defaults to 1):377                    Used to set the maximum value of the initial timestamp. This is used to prevent the model from378                    predicting timestamps that are too far in the future.379    """380 381    def __init__(self, generate_config, model_config, decoder_input_length):382        self.eos_token_id = generate_config.eos_token_id383        self.no_timestamps_token_id = generate_config.no_timestamps_token_id384        self.timestamp_begin = generate_config.no_timestamps_token_id + 1385 386        self.begin_index = decoder_input_length + 1387 388        if generate_config.is_multilingual:389            # room for language token and task token390            self.begin_index += 2391        if hasattr(generate_config, "max_initial_timestamp_index"):392            self.max_initial_timestamp_index = generate_config.max_initial_timestamp_index393        else:394            self.max_initial_timestamp_index = model_config.vocab_size395        if self.max_initial_timestamp_index is None:396            self.max_initial_timestamp_index = model_config.vocab_size397 398    def __call__(self, input_ids, scores, cur_len):399        # suppress <|notimestamps|> which is handled by without_timestamps400        scores = scores.at[:, self.no_timestamps_token_id].set(-float("inf"))401 402        def handle_pairs(input_ids_k, scores_k):403            last_was_timestamp = jnp.where((cur_len - self.begin_index) >= 1, True, False)404            last_was_timestamp = jnp.where(405                input_ids_k[cur_len - 1] >= self.timestamp_begin,406                True and last_was_timestamp,407                False,408            )409 410            penultimate_was_timestamp = jnp.where((cur_len - self.begin_index) < 2, True, False)411            penultimate_was_timestamp = jnp.where(412                input_ids_k[cur_len - 2] >= self.timestamp_begin,413                True,414                penultimate_was_timestamp,415            )416 417            return jnp.where(418                last_was_timestamp,419                jnp.where(420                    penultimate_was_timestamp > 0,421                    scores_k.at[self.timestamp_begin :].set(-float("inf")),422                    scores_k.at[: self.eos_token_id].set(-float("inf")),423                ),424                scores_k,425            )426 427        scores = jax.vmap(handle_pairs)(input_ids, scores)428 429        apply_max_initial_timestamp = jnp.where(cur_len == self.begin_index, True, False)430        apply_max_initial_timestamp = jnp.where(431            self.max_initial_timestamp_index is not None,432            True and apply_max_initial_timestamp,433            False,434        )435 436        last_allowed = self.timestamp_begin + self.max_initial_timestamp_index437 438        scores = jnp.where(439            apply_max_initial_timestamp,440            scores.at[:, last_allowed + 1 :].set(-float("inf")),441            scores,442        )443 444        # if sum of probability over timestamps is above any other token, sample timestamp445        logprobs = jax.nn.log_softmax(scores, axis=-1)446 447        def handle_cumulative_probs(logprobs_k, scores_k):448            timestamp_logprob = jax.nn.logsumexp(logprobs_k[self.timestamp_begin :], axis=-1)449            max_text_token_logprob = jnp.max(logprobs_k[: self.timestamp_begin])450            return jnp.where(451                timestamp_logprob > max_text_token_logprob,452                scores_k.at[: self.timestamp_begin].set(-float("inf")),453                scores_k,454            )455 456        scores = jax.vmap(handle_cumulative_probs)(logprobs, scores)457 458        return scores459 460 461class FlaxNoRepeatNGramLogitsProcessor(FlaxLogitsProcessor):462    r"""463    [`FlaxLogitsProcessor`] that enforces no repetition of n-grams. See464    [Fairseq](https://github.com/pytorch/fairseq/blob/a07cb6f40480928c9e0548b737aadd36ee66ac76/fairseq/sequence_generator.py#L345).465 466    Args:467        ngram_size (`int`):468            All ngrams of size `ngram_size` can only occur once.469    """470 471    def __init__(self, ngram_size: int):472        if not isinstance(ngram_size, int) or ngram_size <= 0:473            raise ValueError(f"`ngram_size` has to be a strictly positive integer, but is {ngram_size}")474        self.ngram_size = ngram_size475 476    def get_previous_ngrams(self, input_ids: jnp.ndarray, vocab_size: int, cur_len: int):477        """478        get a matrix of size (batch_size,) + (vocab_size,)*n (for n-grams) that479        represent the n-grams that occurred previously.480        The BCOO representation allow to store only the few non-zero entries, instead of the full (huge) matrix481        """482        batch_size, seq_len = input_ids.shape483        # number of n-grams in the whole sequence484        seq_ngrams = seq_len - (self.ngram_size - 1)485        # number of n-grams in the currently generated sequence486        cur_ngrams = cur_len - (self.ngram_size - 1)487 488        def body_fun(i, val):489            b = i % batch_size490            pos = i // batch_size491            return val.at[i].set(492                jnp.array(493                    [494                        b,495                    ]496                    + [jnp.array(input_ids)[b, pos + j] for j in range(self.ngram_size)]497                )498            )499 500        shape = (batch_size * seq_ngrams, self.ngram_size + 1)501        all_update_indices = jax.lax.fori_loop(502            0, batch_size * cur_ngrams, body_fun, jnp.zeros(shape, dtype=input_ids.dtype)503        )504 505        # ignore the n-grams not yet generated506        data = (jnp.arange(batch_size * seq_ngrams) < batch_size * cur_ngrams).astype("float32")507 508        return sparse.BCOO((data, all_update_indices), shape=(batch_size,) + (vocab_size,) * self.ngram_size)509 510    def get_banned_tokens_mask(self, latest_tokens: jnp.ndarray, previous_ngrams) -> jnp.ndarray:511        """512        Determines which tokens must be banned given latest tokens and the previously seen513        ngrams.514        """515 516        @sparse.sparsify517        @jax.vmap518        def inner_fn(latest_tokens, previous_ngrams):519            return previous_ngrams[tuple(latest_tokens)]520 521        return sparse.bcoo_todense(inner_fn(latest_tokens, previous_ngrams))522 523    def __call__(self, input_ids: jnp.ndarray, scores: jnp.ndarray, cur_len: int) -> jnp.ndarray:524        def true_fn():525            _, vocab_size = scores.shape526            # store the previously seen n-grams527            previous_ngrams = self.get_previous_ngrams(input_ids, vocab_size, cur_len)528 529            # get the n-1 last tokens that prefix the n-gram being generated530            latest_tokens = jnp.zeros((input_ids.shape[0], self.ngram_size - 1), dtype=input_ids.dtype)531            latest_tokens = jax.lax.dynamic_update_slice(532                latest_tokens,533                jax.lax.dynamic_slice(534                    input_ids, (0, cur_len - (self.ngram_size - 1)), (input_ids.shape[0], (self.ngram_size - 1))535                ),536                (0, 0),537            )538 539            # compute the banned tokens, ie all the tokens that when added to the latest tokens lead to a n-gram that was previously generated540            banned_tokens_indices_mask = self.get_banned_tokens_mask(latest_tokens, previous_ngrams).astype("bool")541            return jnp.where(banned_tokens_indices_mask, -float("inf"), scores)542 543        output = jax.lax.cond((cur_len >= self.ngram_size - 1), true_fn, lambda: scores)544        return output545