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, 13:30 UTC
[INTEL-HPU][v0] Port delayed sampling to upstream (#16949)
Signed-off-by: Michal Adamczyk <michal.adamczyk@intel.com> Signed-off-by: Chendi Xue <chendi.xue@intel.com> Co-authored-by: Michal Adamczyk <madamczyk@habana.ai>
Chendi.Xue committed
Apr 22, 2025 at 22:14 UTC
56a735261c17d04529bca08d8245b1d383bae5f0
2 files changed
+140
-7
vllm/envs.py
+7
@@ -98,6 +98,7 @@ if TYPE_CHECKING:
98
VLLM_RAY_BUNDLE_INDICES: str = ""
99
VLLM_CUDART_SO_PATH: Optional[str] = None
100
VLLM_USE_HPU_CONTIGUOUS_CACHE_FETCH: bool = True
101
+ VLLM_HPU_USE_DELAYED_SAMPLING: bool = False
102
VLLM_DP_RANK: int = 0
103
VLLM_DP_RANK_LOCAL: int = -1
104
VLLM_DP_SIZE: int = 1
@@ -650,6 +651,12 @@ environment_variables: dict[str, Callable[[], Any]] = {
651
lambda: os.environ.get("VLLM_CONTIGUOUS_PA", "true").lower() in
652
("1", "true"),
653
654
+ # Use delayed sampling for HPU to reduce host cpu overhead
655
+ # between each step.
656
+ "VLLM_HPU_USE_DELAYED_SAMPLING":
657
+ lambda: os.environ.get("VLLM_DELAYED_SAMPLING", "false").lower() in
658
+ ("1", "true"),
659
+
660
# Rank of the process in the data parallel setting
661
"VLLM_DP_RANK":
662
lambda: int(os.getenv("VLLM_DP_RANK", "0")),
vllm/worker/hpu_model_runner.py
+133
-7
@@ -74,6 +74,8 @@ _PAD_BLOCK_ID = 0
74
75
LORA_WARMUP_RANK = 8
76
77
+DUMMY_TOKEN_ID = -1
78
+
79
80
class Singleton(type):
81
_instances: Dict[type, object] = {}
@@ -668,6 +670,9 @@ class HPUModelRunnerBase(ModelRunnerBase[TModelInputForHPU]):
670
671
# For multi-step scheduling
672
self.cached_step_outputs: List[torch.Tensor] = []
673
+ # For delayed sampling
674
+ self.cached_step_inputs: List[
675
+ ModelInputForHPUWithSamplingMetadata] = []
676
677
def _set_gc_threshold(self) -> None:
678
# Read https://docs.python.org/3/library/gc.html#gc.set_threshold
@@ -771,6 +776,12 @@ class HPUModelRunnerBase(ModelRunnerBase[TModelInputForHPU]):
776
msg = f"Loading model weights took in total {m.get_summary_string()}"
777
logger.info(msg)
778
779
+ def _maybe_wrap_in_hpu_graph(self, *args, **kwargs):
780
+ return htorch.hpu.wrap_in_hpu_graph(
781
+ HpuModelAdapter(*args, **kwargs), disable_tensor_cache=True
782
+ ) if htorch.utils.internal.is_lazy() else HpuModelAdapter(
783
+ *args, **kwargs)
784
+
785
def get_model(self) -> nn.Module:
786
return self.model
787
@@ -2020,6 +2031,21 @@ class HPUModelRunner(HPUModelRunnerBase[ModelInputForHPUWithSamplingMetadata]):
2031
2032
return lora_mask, lora_logits_mask
2033
2034
+ def _get_seq_ids(self, model_input):
2035
+ return ([
2036
+ sg.seq_ids[0] for sg in model_input.sampling_metadata.seq_groups
2037
+ ])
2038
+
2039
+ def _pad_to_max_num_seqs(self, tensor, value):
2040
+ padding_needed = self.max_num_seqs - tensor.size(0)
2041
+ if padding_needed:
2042
+ padding = torch.full((padding_needed, *tensor.shape[1:]),
2043
+ value,
2044
+ device=tensor.device,
2045
+ dtype=tensor.dtype)
2046
+ tensor = torch.cat([tensor, padding])
2047
+ return tensor
2048
+
2049
@torch.inference_mode()
2050
def execute_model(
2051
self,
@@ -2030,6 +2056,37 @@ class HPUModelRunner(HPUModelRunnerBase[ModelInputForHPUWithSamplingMetadata]):
2056
warmup_mode=False,
2057
seqs=None,
2058
) -> Optional[Union[List[SamplerOutput], IntermediateTensors]]:
2059
+ VLLM_DELAYED_SAMPLING = envs.VLLM_HPU_USE_DELAYED_SAMPLING
2060
+ use_delayed_sampling = VLLM_DELAYED_SAMPLING and not warmup_mode
2061
+ assert not (use_delayed_sampling and num_steps != 1), \
2062
+ 'Delayed sampling is not compatible with MSS!'
2063
+ assert model_input.input_tokens is not None
2064
+ if use_delayed_sampling and not model_input.is_prompt and \
2065
+ self.is_driver_worker:
2066
+ num_cached = len(self.cached_step_outputs)
2067
+ assert num_cached > 0
2068
+ cur_seq_ids = self._get_seq_ids(model_input)
2069
+ cur_seq_id_pos = {
2070
+ sid: idx
2071
+ for idx, sid in enumerate(cur_seq_ids) if sid >= 0
2072
+ }
2073
+ htorch.core.mark_step()
2074
+ for i in range(num_cached):
2075
+ prev_seq_ids = self._get_seq_ids(self.cached_step_inputs[i])
2076
+ target_indices = [
2077
+ cur_seq_id_pos.get(psi, -1) for psi in prev_seq_ids
2078
+ ]
2079
+ padding = self.cached_step_outputs[i].size(0) - len(
2080
+ target_indices)
2081
+ target_indices.extend([-1] * padding)
2082
+ target_indices = torch.tensor(
2083
+ target_indices,
2084
+ device=model_input.input_tokens.device,
2085
+ dtype=model_input.input_tokens.dtype)
2086
+ model_input.input_tokens.index_copy_(
2087
+ 0, target_indices, self.cached_step_outputs[i])
2088
+ htorch.core.mark_step()
2089
+
2090
if not model_input.is_first_multi_step:
2091
if not model_input.is_last_step:
2092
# not first or last multi-step
@@ -2045,7 +2102,21 @@ class HPUModelRunner(HPUModelRunnerBase[ModelInputForHPUWithSamplingMetadata]):
2102
assert model_input.lora_mapping is not None
2103
self.set_active_loras(model_input.lora_requests,
2104
model_input.lora_mapping)
2048
- input_tokens = model_input.input_tokens
2105
+ # Rank!=0 workers has is_prompt==None
2106
+ if use_delayed_sampling and not model_input.is_prompt and \
2107
+ model_input.input_tokens.size(1) == 1:
2108
+ if self.is_driver_worker:
2109
+ model_kwargs_broadcast_data = {
2110
+ "input_tokens": model_input.input_tokens
2111
+ }
2112
+ broadcast_tensor_dict(model_kwargs_broadcast_data, src=0)
2113
+ input_tokens = model_input.input_tokens
2114
+
2115
+ else:
2116
+ model_kwargs_broadcast_data = broadcast_tensor_dict(src=0)
2117
+ input_tokens = model_kwargs_broadcast_data["input_tokens"]
2118
+ else:
2119
+ input_tokens = model_input.input_tokens
2120
input_positions = model_input.input_positions
2121
attn_metadata = model_input.attn_metadata
2122
sampling_metadata = model_input.sampling_metadata
@@ -2092,7 +2163,7 @@ class HPUModelRunner(HPUModelRunnerBase[ModelInputForHPUWithSamplingMetadata]):
2163
f"graphs{'T' if use_graphs else 'F'}")
2164
else:
2165
model_event_name = 'model_executable'
2095
- if num_steps > 1:
2166
+ if num_steps > 1 or use_delayed_sampling:
2167
# in case of multi-step scheduling
2168
# we only want to pythonize in the last step
2169
sampling_metadata.skip_sampler_cpu_output = True
@@ -2152,9 +2223,9 @@ class HPUModelRunner(HPUModelRunnerBase[ModelInputForHPUWithSamplingMetadata]):
2223
if not self.is_driver_worker:
2224
continue
2225
2155
- if model_input.async_callback is not None:
2156
- model_input.async_callback()
2157
- # Sample the next token.
2226
+ if use_delayed_sampling:
2227
+ fake_output = self._delayed_sampler_outputs(model_input)
2228
+
2229
with self.profiler.record_event(
2230
'internal', ('sample_'
2231
f'{"prompt" if is_prompt else "decode"}_'
@@ -2166,9 +2237,16 @@ class HPUModelRunner(HPUModelRunnerBase[ModelInputForHPUWithSamplingMetadata]):
2237
)
2238
if num_steps > 1:
2239
output = output.sampled_token_ids
2169
- self.cached_step_outputs.append(
2170
- output.detach().clone())
2240
+ self.cached_step_outputs.append(output)
2241
+ if use_delayed_sampling and self.is_driver_worker:
2242
+ self._patch_prev_output()
2243
+ output = self._pad_to_max_num_seqs(
2244
+ output.sampled_token_ids, DUMMY_TOKEN_ID)
2245
+ self.cached_step_outputs.append(output)
2246
+ self.cached_step_inputs.append(model_input)
2247
htorch.core.mark_step()
2248
+ if model_input.async_callback is not None:
2249
+ model_input.async_callback()
2250
if i < num_steps - 1:
2251
if i == 0:
2252
if model_input.async_callback is not None:
@@ -2241,11 +2319,30 @@ class HPUModelRunner(HPUModelRunnerBase[ModelInputForHPUWithSamplingMetadata]):
2319
is_prompt=is_prompt)
2320
self.profiler.record_counter(self.event_start, counters)
2321
if num_steps == 1:
2322
+ if self.return_hidden_states:
2323
+ # we only need to pass hidden states of most recent token
2324
+ assert model_input.sampling_metadata is not None
2325
+ if model_input.is_prompt:
2326
+ output.prefill_hidden_states = hidden_states
2327
+ output.hidden_states = hidden_states
2328
+ if use_delayed_sampling:
2329
+ if self.is_driver_worker:
2330
+ return [fake_output]
2331
+ else:
2332
+ return []
2333
+
2334
return [output] if self.is_driver_worker else []
2335
else:
2336
return []
2337
return output if type(output) is list else [output]
2338
2339
+ def _delayed_sampler_outputs(self, model_input):
2340
+ next_token_ids = [[DUMMY_TOKEN_ID]] * len(
2341
+ model_input.sampling_metadata.seq_groups)
2342
+ sampler_output = self._make_decode_output(
2343
+ next_token_ids, model_input.sampling_metadata.seq_groups)
2344
+ return sampler_output
2345
+
2346
def _decode_sampler_outputs(self, model_input):
2347
use_async_out_proc = model_input.async_callback is not None
2348
sampler_outputs = []
@@ -2312,3 +2409,32 @@ class HPUModelRunner(HPUModelRunnerBase[ModelInputForHPUWithSamplingMetadata]):
2409
2410
def __del__(self):
2411
self.shutdown_inc()
2412
+
2413
+ def _patch_prev_output(self):
2414
+ assert len(self.cached_step_inputs) == len(self.cached_step_outputs), \
2415
+ f'''Inputs and outputs are out of sync!
2416
+ {len(self.cached_step_inputs)} vs {len(self.cached_step_outputs)}'''
2417
+ if len(self.cached_step_inputs) == 0:
2418
+ return
2419
+ model_input = self.cached_step_inputs.pop(0)
2420
+ delayed_output = self.cached_step_outputs.pop(0).cpu().squeeze(
2421
+ -1).tolist()
2422
+ ctx = model_input.async_callback.keywords["ctx"] # type: ignore
2423
+ # If there's no output to patch with, which is usually the case when
2424
+ # we're starting a new request after all requests are completed.
2425
+ if len(ctx.output_queue) == 0:
2426
+ return
2427
+ assert len(
2428
+ ctx.output_queue) == 1, 'There should be exactly 1 output waiting!'
2429
+ output_data = ctx.output_queue[0]
2430
+ assert len(output_data.outputs) == 1
2431
+ for fake_out, real_out in zip(output_data.outputs[0], delayed_output):
2432
+ fake_out.samples[0].output_token = real_out
2433
+ for sg, real_out in zip(output_data.seq_group_metadata_list,
2434
+ delayed_output):
2435
+ assert len(sg.seq_data) == 1
2436
+ seq_data = list(sg.seq_data.values())[0]
2437
+ # This is a hack. Assigning output_token_ids triggers
2438
+ # a cache recomputation and we only need to update the last token
2439
+ seq_data.output_token_ids_array[-1] = real_out
2440
+ seq_data._cached_all_token_ids[-1] = real_out