@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 34b2cf3b33
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 15, 14:45 UTC

[Hardware][Intel-Gaudi] Multi-step scheduling implementation for HPU (#12779)

Signed-off-by: Tomasz Zielinski <tomasz.zielinski@intel.com>

Tomasz Zielinski committed Apr 11, 2025 at 16:38 UTC 34b2cf3b3366443e6b68a43572aee1800f6f2b90
3 files changed +439 -116
vllm/platforms/hpu.py
+3 -3
@@ -46,15 +46,15 @@ class HpuPlatform(Platform):
46 def check_and_update_config(cls, vllm_config: VllmConfig) -> None:
47
48 scheduler_config = vllm_config.scheduler_config
49 + parallel_config = vllm_config.parallel_config
50 if scheduler_config.is_multi_step:
50 - raise NotImplementedError(
51 - "Multi-step execution is not implemented for HPU")
51 + parallel_config.worker_cls = \
52 + "vllm.worker.multi_step_hpu_worker.MultiStepHPUWorker"
53
54 if vllm_config.speculative_config is not None:
55 raise NotImplementedError(
56 "Speculative decoding is not implemented for HPU")
57
57 - parallel_config = vllm_config.parallel_config
58 if parallel_config.worker_cls == "auto":
59 parallel_config.worker_cls = "vllm.worker.hpu_worker.HPUWorker"
60
vllm/worker/hpu_model_runner.py
+314 -113
@@ -32,6 +32,7 @@ from vllm_hpu_extension.profiler import (HabanaHighLevelProfiler,
32 import vllm.envs as envs
33 from vllm.attention import AttentionMetadata, get_attn_backend
34 from vllm.config import DeviceConfig, VllmConfig
35 +from vllm.distributed import broadcast_tensor_dict
36 from vllm.distributed.parallel_state import get_world_group
37 from vllm.forward_context import set_forward_context
38 from vllm.logger import init_logger
@@ -44,11 +45,13 @@ from vllm.model_executor.layers.sampler import SamplerOutput
45 from vllm.model_executor.layers.vocab_parallel_embedding import (
46 VocabParallelEmbedding)
47 from vllm.model_executor.model_loader import get_model
48 +from vllm.model_executor.sampling_metadata import SequenceGroupToSample
49 from vllm.multimodal import (MULTIMODAL_REGISTRY, BatchedTensorInputs,
50 MultiModalKwargs)
51 from vllm.sampling_params import SamplingParams
50 -from vllm.sequence import (IntermediateTensors, SequenceData,
51 - SequenceGroupMetadata)
52 +from vllm.sequence import (CompletionSequenceGroupOutput, IntermediateTensors,
53 + Logprob, SequenceData, SequenceGroupMetadata,
54 + SequenceOutput)
55 from vllm.utils import (bind_kv_cache, is_pin_memory_available,
56 make_tensor_with_pad)
57 from vllm.worker.model_runner_base import (
@@ -100,7 +103,10 @@ def subtuple(obj: object,
103 if to_override is None:
104 to_override = {}
105 fields = set(to_copy) | set(to_override.keys())
103 - values = {f: to_override.get(f, getattr(obj, f)) for f in fields}
106 + if type(obj) is dict:
107 + values = {key: obj[key] for key in fields if key in obj}
108 + else:
109 + values = {f: to_override.get(f, getattr(obj, f)) for f in fields}
110 if typename not in _TYPE_CACHE:
111 _TYPE_CACHE[typename] = collections.namedtuple(typename,
112 ' '.join(fields))
@@ -533,6 +539,8 @@ class ModelInputForHPU(ModelRunnerInputBase):
539 virtual_engine: int = 0
540 lora_ids: Optional[List[int]] = None
541 async_callback: Optional[Callable] = None
542 + is_first_multi_step: bool = True
543 + is_last_step: bool = True
544
545 def as_broadcastable_tensor_dict(self) -> Dict[str, Any]:
546 tensor_dict = {
@@ -545,6 +553,8 @@ class ModelInputForHPU(ModelRunnerInputBase):
553 "batch_size_padded": self.batch_size_padded,
554 "virtual_engine": self.virtual_engine,
555 "lora_ids": self.lora_ids,
556 + "is_first_multi_step": self.is_first_multi_step,
557 + "is_last_step": self.is_last_step,
558 }
559 _add_attn_metadata_broadcastable_dict(tensor_dict, self.attn_metadata)
560 return tensor_dict
@@ -656,6 +666,9 @@ class HPUModelRunnerBase(ModelRunnerBase[TModelInputForHPU]):
666 self._set_gc_threshold()
667 self.use_contiguous_pa = envs.VLLM_USE_HPU_CONTIGUOUS_CACHE_FETCH
668
669 + # For multi-step scheduling
670 + self.cached_step_outputs: List[torch.Tensor] = []
671 +
672 def _set_gc_threshold(self) -> None:
673 # Read https://docs.python.org/3/library/gc.html#gc.set_threshold
674 # for comprehensive description of gc generations.
@@ -1005,6 +1018,7 @@ class HPUModelRunnerBase(ModelRunnerBase[TModelInputForHPU]):
1018 def _prepare_decode(
1019 self,
1020 seq_group_metadata_list: List[SequenceGroupMetadata],
1021 + output=None,
1022 ) -> PrepareDecodeMetadata:
1023 input_tokens: List[List[int]] = []
1024 input_positions: List[List[int]] = []
@@ -1035,8 +1049,9 @@ class HPUModelRunnerBase(ModelRunnerBase[TModelInputForHPU]):
1049
1050 for seq_id in seq_ids:
1051 seq_data = seq_group_metadata.seq_data[seq_id]
1038 - generation_token = seq_data.get_last_token_id()
1039 - input_tokens.append([generation_token])
1052 + if output is None:
1053 + generation_token = seq_data.get_last_token_id()
1054 + input_tokens.append([generation_token])
1055
1056 seq_len = seq_data.get_len()
1057 position = seq_len - 1
@@ -1047,6 +1062,9 @@ class HPUModelRunnerBase(ModelRunnerBase[TModelInputForHPU]):
1062 seq_lens.append(seq_len)
1063
1064 block_table = seq_group_metadata.block_tables[seq_id]
1065 + num_fully_occupied_blocks = position // self.block_size
1066 + block_table = block_table[:num_fully_occupied_blocks + 1]
1067 +
1068 if len(block_table) == 0:
1069 block_number = _PAD_BLOCK_ID
1070 else:
@@ -1066,9 +1084,14 @@ class HPUModelRunnerBase(ModelRunnerBase[TModelInputForHPU]):
1084 block_table = block_table[-sliding_window_blocks:]
1085 block_tables.append(block_table)
1086
1069 - input_tokens = torch.tensor(input_tokens,
1070 - dtype=torch.long,
1071 - device=self.device)
1087 + if output is None:
1088 + input_tokens = torch.tensor(input_tokens,
1089 + dtype=torch.long,
1090 + device=self.device)
1091 + else:
1092 + real_batch_size = len(seq_group_metadata_list)
1093 + input_tokens = output[:real_batch_size]
1094 +
1095 input_positions = torch.tensor(input_positions,
1096 dtype=torch.long,
1097 device=self.device)
@@ -1462,7 +1485,27 @@ class HPUModelRunnerBase(ModelRunnerBase[TModelInputForHPU]):
1485 profiler.start()
1486 for _ in range(times):
1487 inputs = self.prepare_model_input(seqs)
1465 - self.execute_model(inputs, None, warmup_mode=True)
1488 + is_single_step = \
1489 + self.vllm_config.scheduler_config.num_scheduler_steps == 1
1490 + if is_prompt or is_single_step:
1491 + self.execute_model(inputs, None, warmup_mode=True)
1492 + else: # decode with multi-step
1493 + inputs = dataclasses.replace(inputs,
1494 + is_first_multi_step=True,
1495 + is_last_step=False)
1496 + self.execute_model(inputs,
1497 + None,
1498 + warmup_mode=True,
1499 + num_steps=2,
1500 + seqs=seqs)
1501 + inputs = dataclasses.replace(inputs,
1502 + is_first_multi_step=False,
1503 + is_last_step=True)
1504 + self.execute_model(inputs,
1505 + None,
1506 + warmup_mode=True,
1507 + num_steps=2,
1508 + seqs=seqs)
1509 torch.hpu.synchronize()
1510 if profiler:
1511 profiler.step()
@@ -1985,115 +2028,273 @@ class HPUModelRunner(HPUModelRunnerBase[ModelInputForHPUWithSamplingMetadata]):
2028 intermediate_tensors: Optional[IntermediateTensors] = None,
2029 num_steps: int = 1,
2030 warmup_mode=False,
2031 + seqs=None,
2032 ) -> Optional[Union[List[SamplerOutput], IntermediateTensors]]:
1989 - if num_steps > 1:
1990 - raise ValueError(
1991 - "num_steps > 1 is not supported in HPUModelRunner")
2033 + if not model_input.is_first_multi_step:
2034 + if not model_input.is_last_step:
2035 + # not first or last multi-step
2036 + return []
2037 + # last multi-step
2038 + output = self._decode_sampler_outputs(
2039 + model_input) if self.is_driver_worker else []
2040 + torch.hpu.synchronize()
2041 + if model_input.is_first_multi_step:
2042 + # first multi-step
2043 + if self.lora_config:
2044 + assert model_input.lora_requests is not None
2045 + assert model_input.lora_mapping is not None
2046 + self.set_active_loras(model_input.lora_requests,
2047 + model_input.lora_mapping)
2048 + input_tokens = model_input.input_tokens
2049 + input_positions = model_input.input_positions
2050 + attn_metadata = model_input.attn_metadata
2051 + sampling_metadata = model_input.sampling_metadata
2052 + real_batch_size = model_input.real_batch_size
2053 + batch_size_padded = model_input.batch_size_padded
2054 + assert input_tokens is not None
2055 + assert input_positions is not None
2056 + assert sampling_metadata is not None
2057 + assert attn_metadata is not None
2058 + is_prompt = attn_metadata.is_prompt
2059 + assert is_prompt is not None
2060 + batch_size = input_tokens.size(0)
2061 + seq_len = self._seq_len(attn_metadata)
2062 + use_graphs = self._use_graphs(batch_size, seq_len, is_prompt)
2063 + self._check_config(batch_size, seq_len, is_prompt, warmup_mode)
2064 +
2065 + lora_mask: torch.Tensor = None
2066 + lora_logits_mask: torch.Tensor = None
2067 + if self.lora_config:
2068 + assert model_input.lora_ids is not None
2069 + lora_mask, lora_logits_mask = self.create_lora_mask(
2070 + input_tokens, model_input.lora_ids,
2071 + attn_metadata.is_prompt)
2072 +
2073 + execute_model_kwargs = {
2074 + "input_ids": input_tokens,
2075 + "positions": input_positions,
2076 + "attn_metadata": self.trim_attn_metadata(attn_metadata),
2077 + "intermediate_tensors": intermediate_tensors,
2078 + "lora_mask": lora_mask,
2079 + "virtual_engine": model_input.virtual_engine,
2080 + **(model_input.multi_modal_kwargs or {}),
2081 + }
2082 + if htorch.utils.internal.is_lazy():
2083 + execute_model_kwargs.update(
2084 + {"bypass_hpu_graphs": not use_graphs})
2085
1993 - if self.lora_config:
1994 - assert model_input.lora_requests is not None
1995 - assert model_input.lora_mapping is not None
1996 - self.set_active_loras(model_input.lora_requests,
1997 - model_input.lora_mapping)
1998 - input_tokens = model_input.input_tokens
1999 - input_positions = model_input.input_positions
2000 - attn_metadata = model_input.attn_metadata
2001 - sampling_metadata = model_input.sampling_metadata
2002 - real_batch_size = model_input.real_batch_size
2003 - batch_size_padded = model_input.batch_size_padded
2004 - assert input_tokens is not None
2005 - assert input_positions is not None
2006 - assert sampling_metadata is not None
2007 - assert attn_metadata is not None
2008 - is_prompt = attn_metadata.is_prompt
2009 - assert is_prompt is not None
2010 - batch_size = input_tokens.size(0)
2011 - seq_len = self._seq_len(attn_metadata)
2012 - use_graphs = self._use_graphs(batch_size, seq_len, is_prompt)
2013 - self._check_config(batch_size, seq_len, is_prompt, warmup_mode)
2086 + htorch.core.mark_step()
2087 + if self.is_driver_worker:
2088 + model_event_name = ("model_"
2089 + f"{'prompt' if is_prompt else 'decode'}_"
2090 + f"bs{batch_size}_"
2091 + f"seq{seq_len}_"
2092 + f"graphs{'T' if use_graphs else 'F'}")
2093 + else:
2094 + model_event_name = 'model_executable'
2095 + if num_steps > 1:
2096 + # in case of multi-step scheduling
2097 + # we only want to pythonize in the last step
2098 + sampling_metadata.skip_sampler_cpu_output = True
2099 + self.model.model.sampler.include_gpu_probs_tensor = True
2100 + cache_orig_output_tokens_len: List[Dict] = []
2101 +
2102 + def try_revert_dummy_output_tokens():
2103 + if len(cache_orig_output_tokens_len) > 0:
2104 + # Reuse the original output token ids length
2105 + for i, seq_group_metadata in enumerate(
2106 + seq_group_metadata_list):
2107 + for j, data in seq_group_metadata.seq_data.items():
2108 + orig_output_tokens_len = \
2109 + cache_orig_output_tokens_len[i][j]
2110 + data.output_token_ids = \
2111 + data.output_token_ids[:orig_output_tokens_len]
2112 +
2113 + for i in range(num_steps):
2114 + if i != 0 and not self.is_driver_worker:
2115 + broadcast_data = broadcast_tensor_dict(src=0)
2116 + if 'early_exit' in broadcast_data and broadcast_data[
2117 + 'early_exit']:
2118 + return [output] if num_steps == 1 else []
2119 + execute_model_kwargs.update({
2120 + "input_ids":
2121 + broadcast_data["input_ids"],
2122 + "positions":
2123 + broadcast_data["positions"],
2124 + "attn_metadata":
2125 + self.trim_attn_metadata(
2126 + broadcast_data["attn_metadata"])
2127 + })
2128 + with self.profiler.record_event('internal', model_event_name):
2129 + hidden_states = self.model.forward(
2130 + **execute_model_kwargs,
2131 + selected_token_indices=sampling_metadata.
2132 + selected_token_indices)
2133 +
2134 + if self.lora_config:
2135 + LoraMask.setLoraMask(
2136 + lora_logits_mask.index_select(
2137 + 0, sampling_metadata.selected_token_indices))
2138 +
2139 + # Compute the logits.
2140 + with self.profiler.record_event(
2141 + 'internal',
2142 + ('compute_logits_'
2143 + f'{"prompt" if is_prompt else "decode"}_bs'
2144 + f'{batch_size}_'
2145 + f'seq{seq_len}')):
2146 + if num_steps == 1:
2147 + sampling_metadata.selected_token_indices = None
2148 + logits = self.model.compute_logits(hidden_states,
2149 + sampling_metadata)
2150 + htorch.core.mark_step()
2151 + # Only perform sampling in the driver worker.
2152 + if not self.is_driver_worker:
2153 + continue
2154
2015 - lora_mask: torch.Tensor = None
2016 - lora_logits_mask: torch.Tensor = None
2017 - if self.lora_config:
2018 - assert model_input.lora_ids is not None
2019 - lora_mask, lora_logits_mask = self.create_lora_mask(
2020 - input_tokens, model_input.lora_ids, attn_metadata.is_prompt)
2021 -
2022 - execute_model_kwargs = {
2023 - "input_ids": input_tokens,
2024 - "positions": input_positions,
2025 - "attn_metadata": self.trim_attn_metadata(attn_metadata),
2026 - "intermediate_tensors": intermediate_tensors,
2027 - "lora_mask": lora_mask,
2028 - "virtual_engine": model_input.virtual_engine,
2029 - **(model_input.multi_modal_kwargs or {}),
2030 - }
2031 - if htorch.utils.internal.is_lazy():
2032 - execute_model_kwargs.update({"bypass_hpu_graphs": not use_graphs})
2033 -
2034 - htorch.core.mark_step()
2035 - if self.is_driver_worker:
2036 - model_event_name = ("model_"
2037 - f"{'prompt' if is_prompt else 'decode'}_"
2038 - f"bs{batch_size}_"
2039 - f"seq{seq_len}_"
2040 - f"graphs{'T' if use_graphs else 'F'}")
2155 + if model_input.async_callback is not None:
2156 + model_input.async_callback()
2157 + # Sample the next token.
2158 + with self.profiler.record_event(
2159 + 'internal', ('sample_'
2160 + f'{"prompt" if is_prompt else "decode"}_'
2161 + f'bs{batch_size}_'
2162 + f'seq{seq_len}')):
2163 + output = self.model.sample(
2164 + logits=logits,
2165 + sampling_metadata=sampling_metadata,
2166 + )
2167 + if num_steps > 1:
2168 + output = output.sampled_token_ids
2169 + self.cached_step_outputs.append(
2170 + output.detach().clone())
2171 + htorch.core.mark_step()
2172 + if i < num_steps - 1:
2173 + if i == 0:
2174 + if model_input.async_callback is not None:
2175 + ctx = model_input.async_callback.keywords[ # type: ignore
2176 + "ctx"]
2177 + seq_group_metadata_list = \
2178 + ctx.seq_group_metadata_list
2179 + elif seqs is not None:
2180 + seq_group_metadata_list = seqs
2181 + else:
2182 + raise RuntimeError(
2183 + "seq_group_metadata_list is uninitialized")
2184 + for i, seq_group_metadata in enumerate(
2185 + seq_group_metadata_list):
2186 + # Skip empty steps
2187 + seq_group_metadata.state.current_step += (
2188 + num_steps - 2)
2189 + # Cache the original output token ids
2190 + cache_orig_output_tokens_len.append({})
2191 + for j, data in seq_group_metadata.seq_data.items():
2192 + cache_orig_output_tokens_len[i][j] = \
2193 + len(data.output_token_ids)
2194 + for seq_group_metadata in seq_group_metadata_list:
2195 + for data in seq_group_metadata.seq_data.values():
2196 + max_output_len = sampling_metadata.seq_groups[
2197 + 0].sampling_params.max_tokens
2198 + if len(data.output_token_ids) < max_output_len - 1:
2199 + # add a place holder for prepare_decode
2200 + # arbitrary value, this could be any token
2201 + dummy_token = (540, )
2202 + data.output_token_ids += (dummy_token)
2203 + else:
2204 + broadcast_tensor_dict({'early_exit': True},
2205 + src=0)
2206 + if num_steps == 1:
2207 + return [output]
2208 + else:
2209 + try_revert_dummy_output_tokens()
2210 + return []
2211 +
2212 + result = self._prepare_decode(seq_group_metadata_list,
2213 + output=output)
2214 + execute_model_kwargs.update({
2215 + "input_ids":
2216 + result.input_tokens,
2217 + "positions":
2218 + result.input_positions,
2219 + "attn_metadata":
2220 + self.trim_attn_metadata(result.attn_metadata)
2221 + })
2222 + model_kwargs_broadcast_data = {
2223 + "input_ids": result.input_tokens,
2224 + "positions": result.input_positions,
2225 + "attn_metadata": vars(result.attn_metadata)
2226 + }
2227 + broadcast_tensor_dict(model_kwargs_broadcast_data, src=0)
2228 + else:
2229 + try_revert_dummy_output_tokens()
2230 +
2231 + if self.is_driver_worker and self.profiler.enabled:
2232 + # Stop recording 'execute_model' event
2233 + self.profiler.end()
2234 + event_end = self.profiler.get_timestamp_us()
2235 + counters = self.profiler_counter_helper.get_counter_dict(
2236 + cache_config=self.cache_config,
2237 + duration=event_end - self.event_start,
2238 + seq_len=seq_len,
2239 + batch_size_padded=batch_size_padded,
2240 + real_batch_size=real_batch_size,
2241 + is_prompt=is_prompt)
2242 + self.profiler.record_counter(self.event_start, counters)
2243 + if num_steps == 1:
2244 + return [output] if self.is_driver_worker else []
2245 + else:
2246 + return []
2247 + return output if type(output) is list else [output]
2248 +
2249 + def _decode_sampler_outputs(self, model_input):
2250 + use_async_out_proc = model_input.async_callback is not None
2251 + sampler_outputs = []
2252 + num_outputs = len(self.cached_step_outputs)
2253 + for i in range(num_outputs):
2254 + next_token_ids = self.cached_step_outputs.pop(0)
2255 + next_token_ids = next_token_ids.cpu().tolist()
2256 + sampler_output = self._make_decode_output(
2257 + next_token_ids, model_input.sampling_metadata.seq_groups)
2258 + sampler_outputs.append(sampler_output)
2259 +
2260 + if i < num_outputs - 1 and use_async_out_proc:
2261 + assert model_input.async_callback is not None
2262 + ctx = model_input.async_callback.keywords[ # type: ignore
2263 + "ctx"]
2264 + ctx.append_output(
2265 + outputs=[sampler_output],
2266 + seq_group_metadata_list=ctx.seq_group_metadata_list,
2267 + scheduler_outputs=ctx.scheduler_outputs,
2268 + is_async=False,
2269 + is_last_step=False,
2270 + is_first_step_output=False)
2271 + model_input.async_callback()
2272 +
2273 + if use_async_out_proc:
2274 + return [sampler_outputs[-1]]
2275 else:
2042 - model_event_name = 'model_executable'
2043 - with self.profiler.record_event('internal', model_event_name):
2044 - hidden_states = self.model.forward(
2045 - **execute_model_kwargs,
2046 - selected_token_indices=sampling_metadata.selected_token_indices
2047 - )
2276 + return sampler_outputs
2277
2049 - if self.lora_config:
2050 - LoraMask.setLoraMask(
2051 - lora_logits_mask.index_select(
2052 - 0, sampling_metadata.selected_token_indices))
2053 -
2054 - # Compute the logits.
2055 - with self.profiler.record_event(
2056 - 'internal', ('compute_logits_'
2057 - f'{"prompt" if is_prompt else "decode"}_bs'
2058 - f'{batch_size}_'
2059 - f'seq{seq_len}')):
2060 - sampling_metadata.selected_token_indices = None
2061 - logits = self.model.compute_logits(hidden_states,
2062 - sampling_metadata)
2063 - htorch.core.mark_step()
2064 - # Only perform sampling in the driver worker.
2065 - if not self.is_driver_worker:
2066 - return []
2067 -
2068 - if model_input.async_callback is not None:
2069 - model_input.async_callback()
2070 -
2071 - # Sample the next token.
2072 - with self.profiler.record_event(
2073 - 'internal', ('sample_'
2074 - f'{"prompt" if is_prompt else "decode"}_'
2075 - f'bs{batch_size}_'
2076 - f'seq{seq_len}')):
2077 - output = self.model.sample(
2078 - logits=logits,
2079 - sampling_metadata=sampling_metadata,
2080 - )
2081 - output.outputs = output.outputs[:real_batch_size]
2082 - htorch.core.mark_step()
2083 -
2084 - if self.is_driver_worker and self.profiler.enabled:
2085 - # Stop recording 'execute_model' event
2086 - self.profiler.end()
2087 - event_end = self.profiler.get_timestamp_us()
2088 - counters = self.profiler_counter_helper.get_counter_dict(
2089 - cache_config=self.cache_config,
2090 - duration=event_end - self.event_start,
2091 - seq_len=seq_len,
2092 - batch_size_padded=batch_size_padded,
2093 - real_batch_size=real_batch_size,
2094 - is_prompt=is_prompt)
2095 - self.profiler.record_counter(self.event_start, counters)
2096 - return [output]
2278 + def _make_decode_output(
2279 + self,
2280 + next_token_ids: List[List[int]],
2281 + seq_groups: List[SequenceGroupToSample],
2282 + ) -> SamplerOutput:
2283 + zero_logprob = Logprob(0.0)
2284 + sampler_outputs = []
2285 + batch_idx = 0
2286 + for seq_group in seq_groups:
2287 + seq_ids = seq_group.seq_ids
2288 + seq_outputs = []
2289 + for seq_id in seq_ids:
2290 + next_token_id = next_token_ids[batch_idx][0]
2291 + seq_outputs.append(
2292 + SequenceOutput(seq_id, next_token_id,
2293 + {next_token_id: zero_logprob}))
2294 + batch_idx += 1
2295 + sampler_outputs.append(
2296 + CompletionSequenceGroupOutput(seq_outputs, None))
2297 + return SamplerOutput(sampler_outputs)
2298
2299 def shutdown_inc(self):
2300 can_finalize_inc = False
vllm/worker/multi_step_hpu_worker.py new
+122
@@ -0,0 +1,122 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +
3 +###############################################################################
4 +# Copyright (C) 2025 Habana Labs, Ltd. an Intel Company
5 +###############################################################################
6 +
7 +import dataclasses
8 +from typing import Dict, Optional, Tuple
9 +
10 +import torch
11 +
12 +from vllm.distributed import broadcast_tensor_dict
13 +from vllm.sequence import ExecuteModelRequest
14 +from vllm.worker.hpu_model_runner import ModelInputForHPU
15 +from vllm.worker.hpu_worker import HPUWorker
16 +from vllm.worker.worker_base import WorkerInput
17 +
18 +
19 +class MultiStepHPUWorker(HPUWorker):
20 +
21 + def __init__(self, *args, **kwargs):
22 + super().__init__(*args, **kwargs)
23 + self.cached_model_input: Optional[ModelInputForHPU] = None
24 +
25 + def _get_driver_input_and_broadcast(
26 + self, execute_model_req: ExecuteModelRequest
27 + ) -> Tuple[ModelInputForHPU, WorkerInput, Dict[str, torch.Tensor]]:
28 + """
29 + Get the driver input and broadcast it to other workers.
30 + """
31 + assert self.is_driver_worker
32 + assert execute_model_req.virtual_engine == 0
33 +
34 + is_first_multi_step = execute_model_req.is_first_multi_step
35 + is_last_step = execute_model_req.is_last_step
36 +
37 + if is_first_multi_step:
38 + # on first step we prepare the worker input and model input normally
39 + worker_input: WorkerInput = self.prepare_worker_input(
40 + execute_model_req=execute_model_req)
41 + worker_input = dataclasses.replace(
42 + worker_input,
43 + num_steps=execute_model_req.num_lookahead_slots + 1)
44 + model_input: ModelInputForHPU = (
45 + self.model_runner.prepare_model_input(
46 + execute_model_req.seq_group_metadata_list,
47 + execute_model_req.virtual_engine,
48 + execute_model_req.finished_requests_ids))
49 +
50 + if execute_model_req.async_callback:
51 + model_input = dataclasses.replace(
52 + model_input,
53 + async_callback=execute_model_req.async_callback)
54 + else:
55 + # on subsequent steps we reuse the worker input and model input
56 + assert self.cached_model_input is not None
57 + model_input = self.cached_model_input
58 + worker_input = WorkerInput()
59 +
60 + model_input = dataclasses.replace(
61 + model_input,
62 + is_first_multi_step=is_first_multi_step,
63 + is_last_step=is_last_step)
64 +
65 + if self.do_metadata_broadcast:
66 + if is_first_multi_step:
67 + broadcast_data = worker_input.as_broadcastable_tensor_dict()
68 + broadcast_data.update(
69 + model_input.as_broadcastable_tensor_dict())
70 + broadcast_tensor_dict(broadcast_data, src=0)
71 + else:
72 + broadcast_data = {
73 + "is_first_multi_step": is_first_multi_step,
74 + "is_last_step": is_last_step,
75 + }
76 + broadcast_tensor_dict(broadcast_data, src=0)
77 +
78 + # Returning empty dict here to keep this compatible with
79 + # `LocalOrDistributedWorkerBase._get_driver_input_and_broadcast`
80 + return model_input, worker_input, {}
81 +
82 + def prepare_input(
83 + self,
84 + execute_model_req: Optional[ExecuteModelRequest] = None,
85 + ) -> Optional[Tuple[ModelInputForHPU, WorkerInput, Dict[str,
86 + torch.Tensor]]]:
87 + if self.is_driver_worker:
88 + if execute_model_req is None:
89 + if self.do_metadata_broadcast:
90 + # This signals that there's no more requests to process for
91 + # now. All workers are running infinite loop with
92 + # broadcast_tensor_dict, and it stops the loop when the
93 + # driver broadcasts an empty input. Send an empty input to
94 + # notify all other workers to stop their execution loop.
95 + broadcast_tensor_dict({}, src=0)
96 + return None
97 + model_input, worker_input, _ = self._get_driver_input_and_broadcast(
98 + execute_model_req)
99 + if model_input.is_first_multi_step:
100 + self.cached_model_input = model_input
101 + return model_input, worker_input, {}
102 + else:
103 + broadcast_data = broadcast_tensor_dict(src=0)
104 + if not broadcast_data:
105 + return None
106 +
107 + if len(broadcast_data) == 2:
108 + assert self.cached_model_input is not None
109 + self.cached_model_input = dataclasses.replace(
110 + self.cached_model_input,
111 + is_first_multi_step=broadcast_data["is_first_multi_step"],
112 + is_last_step=broadcast_data["is_last_step"])
113 + empty_worker_input = WorkerInput()
114 + return self.cached_model_input, empty_worker_input, {}
115 +
116 + worker_input = WorkerInput.from_broadcasted_tensor_dict(
117 + broadcast_data)
118 + model_input = (
119 + self.model_runner.
120 + make_model_input_from_broadcasted_tensor_dict(broadcast_data))
121 + self.cached_model_input = model_input
122 + return model_input, worker_input, {}