multi_step.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import functools
  2. from typing import Callable, List
  3. from transformers import PreTrainedTokenizer
  4. from aphrodite.common.logger import log_once
  5. from aphrodite.common.sampling_params import SamplingParams
  6. from aphrodite.common.sequence import (Sequence, SequenceGroup,
  7. SequenceGroupOutput, SequenceOutput,
  8. SequenceStatus)
  9. from aphrodite.common.utils import Counter
  10. from aphrodite.engine.output_processor.interfaces import \
  11. SequenceGroupOutputProcessor
  12. from aphrodite.engine.output_processor.stop_checker import StopChecker
  13. from aphrodite.processing.scheduler import Scheduler
  14. from aphrodite.transformers_utils.detokenizer import Detokenizer
  15. class MultiStepOutputProcessor(SequenceGroupOutputProcessor):
  16. """SequenceGroupOutputProcessor which handles logic related to
  17. detokenization and stopping conditions. It specializes to "multi-step
  18. decoding", where Aphrodite's worker may generate multiple tokens per
  19. invocation. This is currently mutually exclusive with advanced sampling
  20. techniques like beam search, which motivates the separation of this logic
  21. from the single step output processor.
  22. This class is responsible for things such as correctly appending all new
  23. token ids to their sequence, detokenizing new token ids, truncating new
  24. output tokens after an eos token, and correctly handling the case where the
  25. number of new output tokens per sequence differs in a single batch.
  26. """
  27. def __init__(
  28. self,
  29. detokenizer: Detokenizer,
  30. scheduler: List[Scheduler],
  31. seq_counter: Counter,
  32. get_tokenizer_for_seq: Callable[[Sequence], PreTrainedTokenizer],
  33. stop_checker: StopChecker,
  34. ):
  35. self.detokenizer = detokenizer
  36. self.scheduler = scheduler
  37. self.seq_counter = seq_counter
  38. self.get_tokenizer_for_seq = get_tokenizer_for_seq
  39. self.stop_checker = stop_checker
  40. def process_prompt_logprob(self, seq_group: SequenceGroup,
  41. outputs: List[SequenceGroupOutput]) -> None:
  42. # TODO: Prompt logprob currently not implemented in multi step
  43. # workers.
  44. self._log_prompt_logprob_unsupported_warning_once()
  45. @staticmethod
  46. @functools.lru_cache()
  47. def _log_prompt_logprob_unsupported_warning_once():
  48. log_once(
  49. level="WARNING",
  50. message="Prompt logprob is not supported by multi step workers. "
  51. "(e.g., speculative decode uses multi step workers).")
  52. def process_outputs(self, sequence_group: SequenceGroup,
  53. outputs: List[SequenceGroupOutput]) -> None:
  54. """Append new tokens in the outputs to sequences in the sequence group.
  55. This only supports sequence groups of size 1. It supports greater than
  56. one new token per sequence.
  57. This applies logic like stop condition checking and detokenization,
  58. including freeing finished sequences. It also handles cases where there
  59. are tokens emitted after the EOS token.
  60. """
  61. seqs = sequence_group.get_seqs(status=SequenceStatus.RUNNING)
  62. assert seqs, "expected running sequences"
  63. assert len(seqs) == 1, (
  64. "Beam search not supported in multi-step decoding.")
  65. seq = seqs[0]
  66. # Since there's only one sequence per sequence group, we can take the
  67. # first sample.
  68. samples = [output.samples[0] for output in outputs]
  69. # -1 means the output token is not valid (eg. due to spec decode
  70. # rejecting tokens).
  71. valid_samples = [
  72. sample for sample in samples if sample.output_token != -1
  73. ]
  74. assert valid_samples
  75. self._process_seq_outputs(seq, valid_samples,
  76. sequence_group.sampling_params)
  77. def _process_seq_outputs(self, seq: Sequence,
  78. valid_samples: List[SequenceOutput],
  79. sampling_params: SamplingParams) -> None:
  80. output_token_ids = [sample.output_token for sample in valid_samples]
  81. output_logprobs = [sample.logprobs for sample in valid_samples]
  82. # Truncate to max_tokens if necessary.
  83. remaining_tokens = sampling_params.max_tokens - (seq.get_output_len() +
  84. len(output_token_ids))
  85. if remaining_tokens < 0:
  86. valid_samples = valid_samples[:remaining_tokens]
  87. output_token_ids = output_token_ids[:remaining_tokens]
  88. # Truncate any tokens after EOS. This is required as spec decode
  89. # generates a fixed number of tokens without evaluating stopping
  90. # conditions within the block. This can cause an eos token to be
  91. # unintentionally ignored.
  92. if not sampling_params.ignore_eos:
  93. eos_token_id = self.get_tokenizer_for_seq(seq).eos_token_id
  94. # Avoiding .index calls as exception throwing in the happy path
  95. # is expensive.
  96. for i in range(len(output_token_ids)):
  97. if output_token_ids[i] == eos_token_id:
  98. output_token_ids = output_token_ids[:i + 1]
  99. valid_samples = valid_samples[:i + 1]
  100. break
  101. # Incrementally append tokens to the sequence, as if we had only one new
  102. # token.
  103. for output_token_id, output_logprob in zip(output_token_ids,
  104. output_logprobs):
  105. seq.append_token_id(
  106. token_id=output_token_id,
  107. logprobs=output_logprob,
  108. )
  109. new_char_count = 0
  110. if sampling_params.detokenize:
  111. new_char_count = self.detokenizer.decode_sequence_inplace(
  112. seq, sampling_params)
  113. self.stop_checker.maybe_stop_sequence(
  114. seq,
  115. new_char_count=new_char_count,
  116. sampling_params=sampling_params)
  117. if seq.is_finished():
  118. break
  119. if seq.is_finished():
  120. for scheduler in self.scheduler:
  121. scheduler.free_seq(seq)