main
py 163 lines 5.07 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 from dataclasses import dataclass
5
6 from vllm.inputs import (
7 DecoderOnlyEngineInput,
8 EncoderDecoderInput,
9 MultiModalInput,
10 TokensInput,
11 mm_input,
12 tokens_input,
13 )
14 from vllm.logprobs import Logprob
15 from vllm.lora.request import LoRARequest
16
17
18 @dataclass
19 class BeamSearchSequence:
20 """A sequence for beam search.
21 It keeps track of the tokens and the log probability of the sequence.
22 The text field is optional and will only be filled when the sequence is
23 about to be returned to the user.
24 """
25
26 orig_prompt: TokensInput | MultiModalInput | EncoderDecoderInput
27
28 # NOTE: Tokens represents decoder tokens in the encoder / decoder case
29 tokens: list[int]
30 logprobs: list[dict[int, Logprob]]
31 lora_request: LoRARequest | None = None
32 cum_logprob: float = 0.0
33 text: str | None = None
34 finish_reason: str | None = None
35 stop_reason: int | str | None = None
36
37 def get_prompt(self):
38 prompt = self.orig_prompt
39
40 if prompt["type"] == "enc_dec":
41 return self._build_encoder_decoder_inputs(prompt)
42
43 # Handle decoder-only inputs
44 prompt_text = prompt.get("prompt")
45 cache_salt = prompt.get("cache_salt")
46
47 if prompt["type"] == "token":
48 return tokens_input(
49 self.tokens,
50 prompt=prompt_text,
51 cache_salt=cache_salt,
52 )
53
54 return mm_input(
55 prompt_token_ids=self.tokens,
56 mm_kwargs=prompt["mm_kwargs"],
57 mm_hashes=prompt["mm_hashes"],
58 mm_placeholders=prompt["mm_placeholders"],
59 prompt=prompt_text,
60 cache_salt=cache_salt,
61 )
62
63 def _build_encoder_decoder_inputs(
64 self, prompt: EncoderDecoderInput
65 ) -> EncoderDecoderInput:
66 """Rebuild the encoder-decoder inputs with the current beam search
67 sequence's tokens.
68
69 FIXME (alex) - the encoder multimodal cache is not properly wired up
70 yet, which means that currently we are running the encoder on every
71 new beam because num_computed_tokens is 0 on each new request. This
72 will be fixed once the cache is correctly implemented.
73 """
74 dec_prompt = prompt["decoder_prompt"]
75
76 # Rebuild decoder prompt with updated tokens,
77 # but keep everything else the same.
78 new_dec_prompt: DecoderOnlyEngineInput
79 if dec_prompt["type"] == "multimodal":
80 new_dec_prompt = mm_input(
81 self.tokens,
82 mm_kwargs=dec_prompt["mm_kwargs"],
83 mm_hashes=dec_prompt["mm_hashes"],
84 mm_placeholders=dec_prompt["mm_placeholders"],
85 prompt=dec_prompt.get("prompt"),
86 cache_salt=dec_prompt.get("cache_salt"),
87 )
88 else:
89 new_dec_prompt = tokens_input(
90 self.tokens,
91 prompt=dec_prompt.get("prompt"),
92 cache_salt=dec_prompt.get("cache_salt"),
93 )
94
95 return EncoderDecoderInput(
96 type="enc_dec",
97 encoder_prompt=prompt["encoder_prompt"],
98 decoder_prompt=new_dec_prompt,
99 )
100
101
102 @dataclass
103 class BeamSearchOutput:
104 """The output of beam search.
105 It contains the list of the best beam search sequences.
106 The length of the list is equal to the beam width.
107 """
108
109 sequences: list[BeamSearchSequence]
110
111
112 class BeamSearchInstance:
113 def __init__(
114 self,
115 prompt: TokensInput | MultiModalInput | EncoderDecoderInput,
116 lora_request: LoRARequest | None = None,
117 logprobs: list[dict[int, Logprob]] | None = None,
118 **kwargs,
119 ):
120 decoder_prompt = (
121 prompt if prompt["type"] != "enc_dec" else prompt["decoder_prompt"]
122 )
123 initial_tokens = decoder_prompt["prompt_token_ids"]
124
125 self.beams: list[BeamSearchSequence] = [
126 BeamSearchSequence(
127 orig_prompt=prompt,
128 tokens=initial_tokens,
129 logprobs=[] if logprobs is None else list(logprobs),
130 lora_request=lora_request,
131 **kwargs,
132 )
133 ]
134 self.completed: list[BeamSearchSequence] = []
135
136
137 def get_beam_search_score(
138 tokens: list[int],
139 cumulative_logprob: float,
140 eos_token_id: int,
141 length_penalty: float = 1.0,
142 ) -> float:
143 """Calculate the beam search score with length penalty.
144
145 Adapted from
146
147 https://github.com/huggingface/transformers/blob/ccb92be23def445f2afdea94c31286f84b89eb5b/src/transformers/generation/beam_search.py#L938
148 """
149 seq_len = len(tokens)
150 if tokens[-1] == eos_token_id:
151 seq_len -= 1
152
153 # An aborted beam may contain only an EOS prompt token.
154 return cumulative_logprob / (max(seq_len, 1) ** length_penalty)
155
156
157 def create_sort_beams_key_function(eos_token_id: int, length_penalty: float):
158 def sort_beams_key(x: BeamSearchSequence) -> float:
159 return get_beam_search_score(
160 x.tokens, x.cum_logprob, eos_token_id, length_penalty
161 )
162
163 return sort_beams_key