main
py 229 lines 8.67 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import asyncio
5 from abc import ABC
6 from collections.abc import AsyncGenerator, Mapping
7
8 import numpy as np
9
10 from vllm import CompletionOutput, RequestOutput
11 from vllm.engine.protocol import EngineClient
12 from vllm.inputs import EngineInput
13 from vllm.lora.request import LoRARequest
14 from vllm.renderers import BaseRenderer
15 from vllm.sampling_params import BeamSearchParams, SamplingParams
16 from vllm.utils import random_uuid
17 from vllm.utils.async_utils import collect_from_async_generator
18
19 from .utils import BeamSearchSequence, create_sort_beams_key_function
20
21
22 class BeamSearchOnlineMixin(ABC):
23 """online serving for beam search."""
24
25 renderer: BaseRenderer
26 engine_client: EngineClient
27
28 async def beam_search(
29 self,
30 prompt: EngineInput,
31 request_id: str,
32 params: BeamSearchParams,
33 lora_request: LoRARequest | None = None,
34 trace_headers: Mapping[str, str] | None = None,
35 session_id: str | None = None,
36 ) -> AsyncGenerator[RequestOutput, None]:
37 beam_width = params.beam_width
38 max_tokens = params.max_tokens
39 ignore_eos = params.ignore_eos
40 temperature = params.temperature
41 length_penalty = params.length_penalty
42 include_stop_str_in_output = params.include_stop_str_in_output
43
44 tokenizer = self.renderer.get_tokenizer()
45 eos_token_id = tokenizer.eos_token_id
46 sort_beams_key = create_sort_beams_key_function(eos_token_id, length_penalty)
47
48 if prompt["type"] == "embeds":
49 raise NotImplementedError("Embedding prompt not supported for beam search")
50
51 # Extract prompt tokens and text based on model type
52 decoder_prompt = (
53 prompt if prompt["type"] != "enc_dec" else prompt["decoder_prompt"]
54 )
55 prompt_text = decoder_prompt.get("prompt")
56 prompt_token_ids = decoder_prompt["prompt_token_ids"]
57
58 tokenized_length = len(prompt_token_ids)
59
60 logprobs_num = 2 * beam_width
61 sampling_params = SamplingParams(
62 logprobs=logprobs_num,
63 max_tokens=1,
64 temperature=temperature,
65 detokenize=False,
66 )
67 all_beams = [
68 BeamSearchSequence(
69 orig_prompt=prompt,
70 tokens=prompt_token_ids,
71 cum_logprob=0,
72 logprobs=[],
73 lora_request=lora_request,
74 )
75 ]
76 completed = []
77
78 for _ in range(max_tokens):
79 tasks = []
80 request_id_batch = f"{request_id}-{random_uuid()}"
81
82 for i, beam in enumerate(all_beams):
83 prompt_item = beam.get_prompt()
84 lora_request_item = beam.lora_request
85 request_id_item = f"{request_id_batch}-beam-{i}"
86 task = asyncio.create_task(
87 collect_from_async_generator(
88 self.engine_client.generate(
89 prompt_item,
90 sampling_params,
91 request_id_item,
92 lora_request=lora_request_item,
93 trace_headers=trace_headers,
94 session_id=session_id,
95 )
96 )
97 )
98 tasks.append(task)
99
100 output = [x[0] for x in await asyncio.gather(*tasks)]
101
102 for result in output:
103 # check for error finish reason and abort beam search
104 if result.outputs[0].finish_reason == "error":
105 # yield error output and terminate beam search
106 yield RequestOutput(
107 request_id=request_id,
108 prompt=prompt_text,
109 outputs=[
110 CompletionOutput(
111 index=0,
112 text="",
113 token_ids=[],
114 cumulative_logprob=None,
115 logprobs=None,
116 finish_reason="error",
117 )
118 ],
119 finished=True,
120 prompt_token_ids=prompt_token_ids,
121 prompt_logprobs=None,
122 )
123 return
124
125 if any(result.outputs[0].finish_reason == "abort" for result in output):
126 for beam in all_beams:
127 beam.finish_reason = "abort"
128 break
129
130 candidates = []
131 for i, result in enumerate(output):
132 current_beam = all_beams[i]
133
134 if result.outputs[0].logprobs is not None:
135 logprobs = result.outputs[0].logprobs[0]
136 for token_id, logprob_obj in logprobs.items():
137 candidate_logprob = (
138 current_beam.cum_logprob + logprob_obj.logprob
139 )
140 if token_id == eos_token_id and not ignore_eos:
141 completed.append(
142 BeamSearchSequence(
143 orig_prompt=prompt,
144 tokens=current_beam.tokens + [eos_token_id]
145 if include_stop_str_in_output
146 else current_beam.tokens,
147 logprobs=current_beam.logprobs + [logprobs],
148 cum_logprob=candidate_logprob,
149 finish_reason="stop",
150 stop_reason=eos_token_id,
151 )
152 )
153 else:
154 candidates.append(
155 (
156 candidate_logprob,
157 int(token_id),
158 current_beam,
159 logprobs,
160 )
161 )
162
163 # Processing non-EOS tokens
164 candidate_logprobs = np.fromiter(
165 (candidate[0] for candidate in candidates),
166 dtype=np.float64,
167 count=len(candidates),
168 )
169 if len(candidates) <= beam_width:
170 topn_idx = np.argsort(-candidate_logprobs)
171 else:
172 topn_idx = np.argpartition(
173 -candidate_logprobs,
174 beam_width - 1,
175 )[:beam_width]
176 topn_idx = topn_idx[np.argsort(-candidate_logprobs[topn_idx])]
177
178 new_beams = []
179 for idx in topn_idx:
180 cum_logprob, token_id, current_beam, logprobs = candidates[int(idx)]
181 new_beams.append(
182 BeamSearchSequence(
183 orig_prompt=prompt,
184 tokens=current_beam.tokens + [token_id],
185 logprobs=current_beam.logprobs + [logprobs],
186 lora_request=current_beam.lora_request,
187 cum_logprob=cum_logprob,
188 )
189 )
190
191 all_beams = new_beams
192 if not all_beams:
193 break
194
195 completed.extend(all_beams)
196 sorted_completed = sorted(completed, key=sort_beams_key, reverse=True)
197 best_beams = sorted_completed[:beam_width]
198
199 for beam in best_beams:
200 if beam.tokens[-1] == eos_token_id and not ignore_eos:
201 # Skip the eos token in the text.
202 tokens = beam.tokens[tokenized_length:-1]
203 else:
204 tokens = beam.tokens[tokenized_length:]
205 beam.text = tokenizer.decode(
206 tokens, skip_special_tokens=params.skip_special_tokens
207 )
208
209 yield RequestOutput(
210 request_id=request_id,
211 prompt=prompt_text,
212 outputs=[
213 CompletionOutput(
214 text=beam.text, # type: ignore
215 cumulative_logprob=beam.cum_logprob,
216 token_ids=beam.tokens[tokenized_length:],
217 index=i,
218 logprobs=beam.logprobs,
219 finish_reason=beam.finish_reason
220 if beam.finish_reason is not None
221 else "length",
222 stop_reason=beam.stop_reason,
223 )
224 for (i, beam) in enumerate(best_beams)
225 ],
226 finished=True,
227 prompt_token_ids=prompt_token_ids,
228 prompt_logprobs=None,
229 )