Skip to content

vllm.tokenizers.detokenizer_utils

Functions:

_get_leading_space_marker(tokenizer)

Read the space marker from the tokenizer's pre_tokenizer config.

Only Metaspace pre_tokenizers (used by SentencePiece-based models like Llama, Mistral, T5) have a replacement character whose leading instance gets stripped by decode(). ByteLevel (GPT-2), BertPreTokenizer (BERT), and others do not have this issue.

Returns the marker character, or None if decode() is safe for single tokens.

Source code in vllm/tokenizers/detokenizer_utils.py
def _get_leading_space_marker(tokenizer: TokenizerLike) -> str | None:
    """Read the space marker from the tokenizer's pre_tokenizer config.

    Only Metaspace pre_tokenizers (used by SentencePiece-based models like
    Llama, Mistral, T5) have a replacement character whose leading instance
    gets stripped by decode(). ByteLevel (GPT-2), BertPreTokenizer (BERT),
    and others do not have this issue.

    Returns the marker character, or None if decode() is safe for single
    tokens.
    """
    cached = getattr(tokenizer, _CACHED_MARKER_KEY, _NOT_CACHED)
    if cached is not _NOT_CACHED:
        return cached  # type: ignore[return-value]

    backend = getattr(tokenizer, "backend_tokenizer", None)
    if backend is None:
        result = None
    else:
        result = None
        try:
            config = json.loads(backend.to_str())
        except Exception:
            pass
        else:
            pre = config.get("pre_tokenizer") or {}
            pre_type = pre.get("type")
            if pre_type == "Metaspace":
                result = pre.get("replacement", "▁")
            elif pre_type == "Sequence":
                for sub in pre.get("pretokenizers", []):
                    if sub.get("type") == "Metaspace":
                        result = sub.get("replacement", "▁")
                        break

    setattr(tokenizer, _CACHED_MARKER_KEY, result)
    return result

_restore_leading_spaces(raw_token, token_str, marker)

Restore leading spaces that decode() stripped from a raw vocab piece.

Source code in vllm/tokenizers/detokenizer_utils.py
def _restore_leading_spaces(raw_token: str, token_str: str, marker: str) -> str:
    """Restore leading spaces that decode() stripped from a raw vocab piece."""
    num_markers = 0
    for ch in raw_token:
        if ch != marker:
            break
        num_markers += 1
    if num_markers == 0:
        return token_str
    existing = len(token_str) - len(token_str.lstrip(" "))
    missing = num_markers - existing
    return " " * missing + token_str if missing > 0 else token_str

convert_ids_list_to_tokens(tokenizer, token_ids)

Detokenize the input ids individually.

Uses decode() for human-readable output, then checks the raw vocab piece via convert_ids_to_tokens() to restore any leading spaces that decode() stripped (SentencePiece add_dummy_prefix inverse).

Parameters:

  • tokenizer

    (TokenizerLike) –

    tokenizer used by model under test

  • token_ids

    (list[int]) –

    convert these tokens (Python list form)

Returns:

  • list[str]

    Python list of token string representations

Source code in vllm/tokenizers/detokenizer_utils.py
def convert_ids_list_to_tokens(
    tokenizer: TokenizerLike,
    token_ids: list[int],
) -> list[str]:
    """Detokenize the input ids individually.

    Uses decode() for human-readable output, then checks the raw vocab
    piece via convert_ids_to_tokens() to restore any leading spaces that
    decode() stripped (SentencePiece add_dummy_prefix inverse).

    Args:
      tokenizer: tokenizer used by model under test
      token_ids: convert these tokens (Python list form)

    Returns:
      Python list of token string representations

    """
    if not token_ids:
        return []
    marker = _get_leading_space_marker(tokenizer)
    if marker is None:
        return [tokenizer.decode([tid]) or "" for tid in token_ids]
    raw_tokens = tokenizer.convert_ids_to_tokens(token_ids)
    return [
        _restore_leading_spaces(raw, tokenizer.decode([tid]) or "", marker)
        for tid, raw in zip(token_ids, raw_tokens)
    ]

convert_prompt_ids_to_tokens(tokenizer, prompt_ids, skip_special_tokens=False)

Converts the prompt ids to tokens and returns the tokens and offsets for incremental detokenization.

Note that not all tokens are converted to strings. Only the tokens that are necessary for incremental detokenization are converted to strings.

Source code in vllm/tokenizers/detokenizer_utils.py
def convert_prompt_ids_to_tokens(
    tokenizer: TokenizerLike,
    prompt_ids: list[int],
    skip_special_tokens: bool = False,
) -> tuple[list[str], int, int]:
    """Converts the prompt ids to tokens and returns the tokens and offsets
    for incremental detokenization.

    Note that not all tokens are converted to strings. Only the tokens that
    are necessary for incremental detokenization are converted to strings.
    """
    # We do not need to convert the whole prompt to tokens.
    # Offset a little more in case we have special tokens.
    new_tokens = tokenizer.convert_ids_to_tokens(
        prompt_ids[-INITIAL_INCREMENTAL_DETOKENIZATION_OFFSET - 2 :],
        skip_special_tokens=skip_special_tokens,
    )
    read_offset = len(new_tokens)
    prefix_offset = max(read_offset - INITIAL_INCREMENTAL_DETOKENIZATION_OFFSET, 0)
    # This is required to guard against out-of-vocab prompt token ids
    _replace_none_with_empty(new_tokens)  # type: ignore[arg-type]
    return new_tokens, prefix_offset, read_offset

detokenize_incrementally(tokenizer, all_input_ids, prev_tokens, prefix_offset, read_offset, skip_special_tokens=False, spaces_between_special_tokens=True)

Detokenizes the input ids incrementally and returns the new tokens and the new text.

If prev_tokens is None, this function will convert the input ids to tokens and return the tokens and the new text. Otherwise, it will return the new tokens and the new text.

This function will also return the new prefix offset and the new read offset to be used in the next iteration.

The offsets are necessary to defeat cleanup algorithms in the decode which decide to add a space or not depending on the surrounding ids.

Parameters:

  • tokenizer

    (TokenizerLike) –

    The tokenizer to use.

  • all_input_ids

    (list[int]) –

    The input ids. The last id is the new token id.

  • prev_tokens

    (list[str] | None) –

    The previous tokens. If None, this function will convert the input ids to tokens and return the tokens and the new text.

  • prefix_offset

    (int) –

    The prefix offset.

  • read_offset

    (int) –

    The read offset.

  • skip_special_tokens

    (bool, default: False ) –

    Whether to skip special tokens.

  • spaces_between_special_tokens

    (bool, default: True ) –

    Whether to add spaces between special tokens.

Source code in vllm/tokenizers/detokenizer_utils.py
def detokenize_incrementally(
    tokenizer: TokenizerLike,
    all_input_ids: list[int],
    prev_tokens: list[str] | None,
    prefix_offset: int,
    read_offset: int,
    skip_special_tokens: bool = False,
    spaces_between_special_tokens: bool = True,
) -> tuple[list[str], str, int, int]:
    """Detokenizes the input ids incrementally and returns the new tokens
    and the new text.

    If `prev_tokens` is None, this function will convert the input ids to
    tokens and return the tokens and the new text. Otherwise, it will return the
    new tokens and the new text.

    This function will also return the new prefix offset and the new read
    offset to be used in the next iteration.

    The offsets are necessary to defeat cleanup algorithms in the decode which
    decide to add a space or not depending on the surrounding ids.

    Args:
        tokenizer: The tokenizer to use.
        all_input_ids: The input ids. The last id is the new token id.
        prev_tokens: The previous tokens. If None, this function will convert
            the input ids to tokens and return the tokens and the new text.
        prefix_offset: The prefix offset.
        read_offset: The read offset.
        skip_special_tokens: Whether to skip special tokens.
        spaces_between_special_tokens: Whether to add spaces between special
            tokens.
    """
    new_token_id = all_input_ids[-1]
    # This is the first iteration for this sequence
    is_first_iter = prev_tokens is None
    if is_first_iter:
        (prev_tokens, prefix_offset, read_offset) = convert_prompt_ids_to_tokens(
            tokenizer, all_input_ids[:-1], skip_special_tokens=skip_special_tokens
        )
    assert prev_tokens is not None

    # If the new token id is out of bounds, return an empty string.
    if 0 <= new_token_id < len(tokenizer):
        # Put new_token_id in a list so skip_special_tokens is respected
        new_tokens = tokenizer.convert_ids_to_tokens(
            [new_token_id], skip_special_tokens=skip_special_tokens
        )
        if isinstance(new_tokens, str):
            new_tokens = [new_tokens]
        else:
            # This is required to guard against out-of-vocab prompt token ids
            # (for example when using dummy weights)
            _replace_none_with_empty(new_tokens)  # type: ignore[arg-type]
    else:
        new_tokens = [""]
    output_tokens = prev_tokens + new_tokens

    # If this is the first iteration, return all tokens.
    if is_first_iter:
        new_tokens = output_tokens

    # The prefix text is necessary only to defeat cleanup algorithms in
    # the decode which decide to add a space or not depending on the
    # surrounding ids.
    if tokenizer.is_fast or not tokenizer.get_added_vocab():
        prefix_text = tokenizer.convert_tokens_to_string(
            output_tokens[prefix_offset:read_offset]
        )
        new_text = tokenizer.convert_tokens_to_string(output_tokens[prefix_offset:])
    else:
        prefix_text = _convert_tokens_to_string_with_added_encoders(
            tokenizer,
            output_tokens[prefix_offset:read_offset],
            skip_special_tokens=skip_special_tokens,
            spaces_between_special_tokens=spaces_between_special_tokens,
        )
        new_text = _convert_tokens_to_string_with_added_encoders(
            tokenizer,
            output_tokens[prefix_offset:],
            skip_special_tokens=skip_special_tokens,
            spaces_between_special_tokens=spaces_between_special_tokens,
        )

    if len(new_text) <= len(prefix_text) or new_text.endswith("�"):
        # utf-8 char at the end means it's a potential unfinished byte sequence
        # from byte fallback tokenization.
        # If it's in the middle, it's probably a real invalid id generated
        # by the model
        return new_tokens, "", prefix_offset, read_offset

    new_text = new_text[len(prefix_text) :]
    return new_tokens, new_text, read_offset, len(output_tokens)