detokenizer.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. from typing import Dict, List, Optional, Tuple, Union
  2. from transformers import PreTrainedTokenizer, PreTrainedTokenizerFast
  3. from aphrodite.common.sequence import (Logprob, SamplingParams, Sequence,
  4. SequenceGroup)
  5. from aphrodite.transformers_utils.tokenizer_group.base_tokenizer_group import \
  6. BaseTokenizerGroup
  7. # Used eg. for marking rejected tokens in spec decoding.
  8. INVALID_TOKEN_ID = -1
  9. class Detokenizer:
  10. """Provides methods to decode the output of a model into text."""
  11. def __init__(self, tokenizer_group: BaseTokenizerGroup):
  12. self.tokenizer_group = tokenizer_group
  13. def get_tokenizer_for_seq(self,
  14. sequence: Sequence) -> "PreTrainedTokenizer":
  15. """Returns the HF tokenizer to use for a given sequence."""
  16. return self.tokenizer_group.get_lora_tokenizer(sequence.lora_request)
  17. def decode_prompt_logprobs_inplace(
  18. self, seq_group: SequenceGroup,
  19. prompt_logprobs: List[Optional[Dict[int, Logprob]]]) -> None:
  20. """Decodes the logprobs for the prompt of a sequence group.
  21. Args:
  22. seq_group: The sequence group to decode.
  23. prompt_logprobs: The logprobs to decode.
  24. Returns:
  25. The prompt logprobs with the decoded tokens.
  26. """
  27. prms = seq_group.sampling_params
  28. # We can pick any sequence for the prompt.
  29. seq = next(iter(seq_group.seqs_dict.values()))
  30. # Only prompt, without the generated token.
  31. all_token_ids = seq.get_token_ids()
  32. prompt_token_ids = all_token_ids[:-1]
  33. tokenizer = self.get_tokenizer_for_seq(seq)
  34. prefix_offset = 0
  35. read_offset = 0
  36. next_iter_prefix_offset = 0
  37. next_iter_read_offset = 0
  38. next_iter_tokens = []
  39. prev_tokens = None
  40. for token_position, prompt_logprobs_for_token in enumerate(
  41. prompt_logprobs):
  42. if not prompt_logprobs_for_token:
  43. continue
  44. for token_id, sample_logprob in prompt_logprobs_for_token.items():
  45. if (sample_logprob.decoded_token is None
  46. and token_id != INVALID_TOKEN_ID):
  47. prompt_token_ids_with_token = (
  48. prompt_token_ids[:token_position] + [token_id])
  49. (new_tokens, new_text, new_prefix_offset,
  50. new_read_offset) = detokenize_incrementally(
  51. tokenizer=tokenizer,
  52. all_input_ids=prompt_token_ids_with_token,
  53. prev_tokens=prev_tokens,
  54. prefix_offset=prefix_offset,
  55. read_offset=read_offset,
  56. skip_special_tokens=prms.skip_special_tokens,
  57. spaces_between_special_tokens=prms.
  58. spaces_between_special_tokens,
  59. )
  60. sample_logprob.decoded_token = new_text
  61. # Use the offsets & prev tokens corresponding to
  62. # real tokens to ensure detokenization is consistent
  63. # actual with prompt.
  64. if token_id == all_token_ids[token_position]:
  65. next_iter_prefix_offset = new_prefix_offset
  66. next_iter_read_offset = new_read_offset
  67. next_iter_tokens = new_tokens
  68. # Advance to the next token position.
  69. prefix_offset = next_iter_prefix_offset
  70. read_offset = next_iter_read_offset
  71. if prev_tokens is None:
  72. prev_tokens = next_iter_tokens
  73. else:
  74. prev_tokens.extend(next_iter_tokens)
  75. def decode_sequence_inplace(self, seq: Sequence,
  76. prms: SamplingParams) -> int:
  77. """Decodes the new token for a sequence. In-place operation.
  78. Args:
  79. seq: The sequence to decode.
  80. prms: The sampling parameters used to generate the sequence.
  81. Returns:
  82. The number of characters added to the output text.
  83. """
  84. all_input_ids = seq.get_token_ids()
  85. token_id_generated_this_iteration = all_input_ids[-1]
  86. tokenizer = self.get_tokenizer_for_seq(seq)
  87. # Convert prompt token IDs to tokens if necessary.
  88. # Do it here so that we don't have to repeat this
  89. # computation for each logprob.
  90. if seq.tokens is None:
  91. (seq.tokens, seq.prefix_offset,
  92. seq.read_offset) = convert_prompt_ids_to_tokens(
  93. tokenizer=tokenizer,
  94. prompt_ids=all_input_ids[:-1],
  95. skip_special_tokens=prms.skip_special_tokens,
  96. )
  97. (new_tokens, new_decoded_token_text, prefix_offset,
  98. read_offset) = detokenize_incrementally(
  99. tokenizer=tokenizer,
  100. all_input_ids=all_input_ids,
  101. prev_tokens=seq.tokens,
  102. prefix_offset=seq.prefix_offset,
  103. read_offset=seq.read_offset,
  104. skip_special_tokens=prms.skip_special_tokens,
  105. spaces_between_special_tokens=prms.spaces_between_special_tokens,
  106. )
  107. # Decode logprobs
  108. logprobs = seq.output_logprobs[-1]
  109. if logprobs:
  110. previous_tokens = all_input_ids[:-1]
  111. for token_id, sample_logprob in logprobs.items():
  112. # If the token was generated this iteration,
  113. # use the provided text.
  114. if token_id == token_id_generated_this_iteration:
  115. sample_logprob.decoded_token = new_decoded_token_text
  116. continue
  117. if (sample_logprob.decoded_token is None
  118. and token_id != INVALID_TOKEN_ID):
  119. all_input_ids_with_logprob = previous_tokens + [token_id]
  120. (_, new_text, _, _) = detokenize_incrementally(
  121. tokenizer=tokenizer,
  122. all_input_ids=all_input_ids_with_logprob,
  123. prev_tokens=seq.tokens,
  124. prefix_offset=seq.prefix_offset,
  125. read_offset=seq.read_offset,
  126. skip_special_tokens=prms.skip_special_tokens,
  127. spaces_between_special_tokens=prms.
  128. spaces_between_special_tokens,
  129. )
  130. sample_logprob.decoded_token = new_text
  131. seq.tokens.extend(new_tokens)
  132. seq.prefix_offset = prefix_offset
  133. seq.read_offset = read_offset
  134. seq.output_text += new_decoded_token_text
  135. return len(new_decoded_token_text)
  136. def _convert_tokens_to_string_with_added_encoders(
  137. tokenizer: Union[PreTrainedTokenizer, PreTrainedTokenizerFast],
  138. output_tokens: List[str],
  139. skip_special_tokens: bool,
  140. spaces_between_special_tokens: bool,
  141. ) -> str:
  142. # Adapted from
  143. # https://github.com/huggingface/transformers/blob/v4.28.0/src/transformers/tokenization_utils.py#L921
  144. # NOTE(woosuk): The following code is slow because it runs a for loop over
  145. # the output_tokens. In Python, running a for loop over a list can be slow
  146. # even when the loop body is very simple.
  147. sub_texts: List[str] = []
  148. current_sub_text: List[str] = []
  149. all_special_tokens = set(tokenizer.all_special_tokens)
  150. for token in output_tokens:
  151. if skip_special_tokens and token in all_special_tokens:
  152. continue
  153. if token in tokenizer.get_added_vocab():
  154. if current_sub_text:
  155. sub_text = tokenizer.convert_tokens_to_string(current_sub_text)
  156. sub_texts.append(sub_text)
  157. current_sub_text = []
  158. sub_texts.append(token)
  159. else:
  160. current_sub_text.append(token)
  161. if current_sub_text:
  162. sub_text = tokenizer.convert_tokens_to_string(current_sub_text)
  163. sub_texts.append(sub_text)
  164. if spaces_between_special_tokens:
  165. return " ".join(sub_texts)
  166. else:
  167. return "".join(sub_texts)
  168. # 5 is an arbitrary value that should work for all
  169. # tokenizers (bigger = more conservative).
  170. INITIAL_INCREMENTAL_DETOKENIZATION_OFFSET = 5
  171. def convert_prompt_ids_to_tokens(
  172. tokenizer: Union[PreTrainedTokenizer, PreTrainedTokenizerFast],
  173. prompt_ids: List[int],
  174. skip_special_tokens: bool = False,
  175. ) -> Tuple[List[str], int, int]:
  176. """Converts the prompt ids to tokens and returns the tokens and offsets
  177. for incremental detokenization.
  178. Note that not all tokens are converted to strings. Only the tokens that
  179. are necessary for incremental detokenization are converted to strings.
  180. """
  181. # We do not need to convert the whole prompt to tokens.
  182. # Offset a little more in case we have special tokens.
  183. new_tokens = tokenizer.convert_ids_to_tokens(
  184. prompt_ids[-INITIAL_INCREMENTAL_DETOKENIZATION_OFFSET - 2:],
  185. skip_special_tokens=skip_special_tokens)
  186. read_offset = len(new_tokens)
  187. prefix_offset = max(
  188. read_offset - INITIAL_INCREMENTAL_DETOKENIZATION_OFFSET, 0)
  189. return new_tokens, prefix_offset, read_offset
  190. # Based on
  191. # https://github.com/huggingface/text-generation-inference/blob/v0.9.4/server/text_generation_server/models/model.py#L62C9-L62C15
  192. # under Apache 2.0 license
  193. def detokenize_incrementally(
  194. tokenizer: Union[PreTrainedTokenizer, PreTrainedTokenizerFast],
  195. all_input_ids: List[int],
  196. prev_tokens: Optional[List[str]],
  197. prefix_offset: int,
  198. read_offset: int,
  199. skip_special_tokens: bool = False,
  200. spaces_between_special_tokens: bool = True,
  201. ) -> Tuple[List[str], str, int, int]:
  202. """Detokenizes the input ids incrementally and returns the new tokens
  203. and the new text.
  204. If `prev_tokens` is None, this function will convert the input ids to
  205. tokens and return the tokens and the new text. Otherwise, it will return the
  206. new tokens and the new text.
  207. This function will also return the new prefix offset and the new read
  208. offset to be used in the next iteration.
  209. The offsets are necessary to defeat cleanup algorithms in the decode which
  210. decide to add a space or not depending on the surrounding ids.
  211. Args:
  212. tokenizer: The tokenizer to use.
  213. all_input_ids: The input ids. The last id is the new token id.
  214. prev_tokens: The previous tokens. If None, this function will convert
  215. the input ids to tokens and return the tokens and the new text.
  216. prefix_offset: The prefix offset.
  217. read_offset: The read offset.
  218. skip_special_tokens: Whether to skip special tokens.
  219. spaces_between_special_tokens: Whether to add spaces between special
  220. tokens.
  221. """
  222. new_token_id = all_input_ids[-1]
  223. # This is the first iteration for this sequence
  224. is_first_iter = prev_tokens is None
  225. if is_first_iter:
  226. (prev_tokens, prefix_offset,
  227. read_offset) = convert_prompt_ids_to_tokens(
  228. tokenizer,
  229. all_input_ids[:-1],
  230. skip_special_tokens=skip_special_tokens)
  231. assert prev_tokens is not None
  232. # If the new token id is out of bounds, return an empty string.
  233. if new_token_id >= len(tokenizer):
  234. new_tokens = [""]
  235. else:
  236. # Put new_token_id in a list so skip_special_tokens is respected
  237. new_tokens = tokenizer.convert_ids_to_tokens(
  238. [new_token_id], skip_special_tokens=skip_special_tokens)
  239. if isinstance(new_tokens, str):
  240. new_tokens = [new_tokens]
  241. output_tokens = prev_tokens + new_tokens
  242. # If this is the first iteration, return all tokens.
  243. if is_first_iter:
  244. new_tokens = output_tokens
  245. # The prefix text is necessary only to defeat cleanup algorithms in
  246. # the decode which decide to add a space or not depending on the
  247. # surrounding ids.
  248. if tokenizer.is_fast or not tokenizer.get_added_vocab():
  249. prefix_text = tokenizer.convert_tokens_to_string(
  250. output_tokens[prefix_offset:read_offset])
  251. new_text = tokenizer.convert_tokens_to_string(
  252. output_tokens[prefix_offset:])
  253. else:
  254. prefix_text = _convert_tokens_to_string_with_added_encoders(
  255. tokenizer,
  256. output_tokens[prefix_offset:read_offset],
  257. skip_special_tokens=skip_special_tokens,
  258. spaces_between_special_tokens=spaces_between_special_tokens,
  259. )
  260. new_text = _convert_tokens_to_string_with_added_encoders(
  261. tokenizer,
  262. output_tokens[prefix_offset:],
  263. skip_special_tokens=skip_special_tokens,
  264. spaces_between_special_tokens=spaces_between_special_tokens,
  265. )
  266. if len(new_text) <= len(prefix_text) or new_text.endswith("�"):
  267. # utf-8 char at the end means it's a potential unfinished byte sequence
  268. # from byte fallback tokenization.
  269. # If it's in the middle, it's probably a real invalid id generated
  270. # by the model
  271. return new_tokens, "", prefix_offset, read_offset
  272. new_text = new_text[len(prefix_text):]
  273. return new_tokens, new_text, read_offset, len(output_tokens)