FoundationVision/LlamaGen
64
1"""A layer that samples the next tokens from the model's outputs."""2import itertools3from typing import Dict, List, Optional, Tuple4 5import torch6import torch.nn as nn7 8from vllm.model_executor.layers.ops.sample import sample as sample_triton9from vllm.model_executor.sampling_metadata import (SamplingMetadata,10 SamplingTensors)11from vllm.sampling_params import SamplingParams, SamplingType12from vllm.sequence import (Logprob, PromptLogprobs, SampleLogprobs,13 SamplerOutput, SequenceData, SequenceGroupOutput,14 SequenceOutput)15 16 17class Sampler(nn.Module):18 """Samples the next tokens from the model's outputs.19 20 This layer does the following:21 1. Discard the hidden states that are not used for sampling (i.e., all22 tokens except the final one in each prompt).23 2. Compute the logits for the next tokens.24 3. Apply presence, frequency and repetition penalties.25 4. Apply temperature scaling.26 5. Apply top-p and top-k truncation.27 6. Sample the next tokens.28 Here, each sequence group within the batch can have different sampling29 parameters (e.g., sampling method, temperature, top-p, top-k, etc.).30 31 The structure of the logits tensor is coupled with the seq_groups in32 sampling_metadata. Typically, each sequence in each seq_group has one row in33 logits for the next token to be sampled; however, for a seq_group with a34 prompt request with the prompt_logprobs sampling parameter, there are rows35 in logits for each token in the input prompt.36 """37 38 def __init__(self, cfg_scale=1.0):39 super().__init__()40 self.cfg_scale = cfg_scale41 # Whether or not the SamplerOutput should have on-device tensors42 # containing the sampled token ids and probabilities. This is used by43 # speculative decoding.44 self.include_gpu_probs_tensor = False45 46 def forward(47 self,48 logits: torch.Tensor,49 sampling_metadata: SamplingMetadata,50 ) -> Optional[SamplerOutput]:51 assert logits is not None52 _, vocab_size = logits.shape53 54 if self.cfg_scale > 1.0:55 logits_combined = logits56 cond_logits, uncond_logits = torch.split(logits_combined, len(logits_combined) // 2, dim=0)57 logits = uncond_logits + (cond_logits - uncond_logits) * self.cfg_scale58 logits = torch.cat([logits, logits], dim=0)59 60 # Apply min_tokens penalty which sets stop tokens to -inf if min_tokens61 # have not been generated yet62 logits = _apply_min_tokens_penalty(logits, sampling_metadata)63 64 # Prepare sampling tensors with pinned memory to avoid blocking.65 (sampling_tensors, do_penalties, do_top_p_top_k,66 do_min_p) = SamplingTensors.from_sampling_metadata(67 sampling_metadata, vocab_size, logits.device, logits.dtype)68 69 # Apply presence and frequency penalties.70 if do_penalties:71 logits = _apply_penalties(logits, sampling_tensors.prompt_tokens,72 sampling_tensors.output_tokens,73 sampling_tensors.presence_penalties,74 sampling_tensors.frequency_penalties,75 sampling_tensors.repetition_penalties)76 77 # Apply temperature scaling.78 # Use in-place division to avoid creating a new tensor.79 logits.div_(sampling_tensors.temperatures.unsqueeze_(dim=1))80 81 if do_top_p_top_k:82 logits = _apply_top_k_top_p(logits, sampling_tensors.top_ps,83 sampling_tensors.top_ks)84 85 if do_min_p:86 logits = _apply_min_p(logits, sampling_tensors.min_ps)87 88 # We use float32 for probabilities and log probabilities.89 # Compute the probabilities.90 probs = torch.softmax(logits, dim=-1, dtype=torch.float)91 # Compute the log probabilities.92 # Use log_softmax to ensure numerical stability.93 logprobs = torch.log_softmax(logits, dim=-1, dtype=torch.float)94 95 # Sample the next tokens.96 sample_results, maybe_sampled_tokens_tensor = _sample(97 probs,98 logprobs,99 sampling_metadata,100 sampling_tensors,101 include_gpu_probs_tensor=self.include_gpu_probs_tensor,102 modify_greedy_probs=self._should_modify_greedy_probs_inplace,103 )104 105 106 if self.cfg_scale > 1.0:107 cond_result = sample_results[:len(sample_results) // 2]108 sample_results = cond_result + cond_result109 110 111 if self.include_gpu_probs_tensor:112 assert maybe_sampled_tokens_tensor is not None113 sampled_tokens_tensor = maybe_sampled_tokens_tensor114 on_device_tensors = (probs, sampled_tokens_tensor)115 else:116 on_device_tensors = None117 118 # Get the logprobs query results.119 prompt_logprobs, sample_logprobs = _get_logprobs(120 logprobs, sampling_metadata, sample_results)121 return _build_sampler_output(sample_results,122 sampling_metadata,123 prompt_logprobs,124 sample_logprobs,125 on_device_tensors=on_device_tensors)126 127 @property128 def _should_modify_greedy_probs_inplace(self) -> bool:129 """Whether or not the sampler should modify the probability distribution130 of greedily-sampled tokens such that multinomial sampling would sample131 the greedily-sampled token.132 133 In other words, if True then we set the probability of the greedily-134 sampled token to 1.135 136 This is used by speculative decoding, which requires that the sampling137 method be encoded into the probability distribution.138 """139 # Modify greedy probs if include_gpu_probs_tensor is set.140 return self.include_gpu_probs_tensor141 142 143def _get_bin_counts_and_mask(144 tokens: torch.Tensor,145 vocab_size: int,146 num_seqs: int,147) -> Tuple[torch.Tensor, torch.Tensor]:148 # Compute the bin counts for the tokens.149 # vocab_size + 1 for padding.150 bin_counts = torch.zeros((num_seqs, vocab_size + 1),151 dtype=torch.long,152 device=tokens.device)153 bin_counts.scatter_add_(1, tokens, torch.ones_like(tokens))154 bin_counts = bin_counts[:, :vocab_size]155 mask = bin_counts > 0156 157 return bin_counts, mask158 159 160def _apply_min_tokens_penalty(161 logits: torch.Tensor,162 sampling_metadata: SamplingMetadata,163) -> torch.Tensor:164 # list of indices in logits that will be set to -inf165 logits_to_penalize = []166 start_idx = 0167 for i, seq_group in enumerate(sampling_metadata.seq_groups):168 seq_ids, sampling_params = seq_group169 170 # handle prompt_logprobs by skipping rows in logits added for the prompt171 # tokens (prompt logprobs are not penalized)172 if (i < sampling_metadata.num_prompts173 and sampling_params.prompt_logprobs is not None):174 assert len(seq_ids) == 1175 start_idx += sampling_metadata.prompt_lens[i] - 1176 177 min_tokens = sampling_params.min_tokens178 if min_tokens > 0:179 seqs_to_penalize = []180 for i, seq_id in enumerate(seq_ids):181 seq_data = sampling_metadata.seq_data[seq_id]182 if len(seq_data.output_token_ids) < min_tokens:183 seqs_to_penalize.append(i)184 185 if seqs_to_penalize:186 # convert to the index into logits187 seqs_to_penalize = [start_idx + i for i in seqs_to_penalize]188 # use set() to remove any duplicates189 token_ids_to_penalize = set(sampling_params.stop_token_ids +190 [sampling_params.eos_token_id])191 # itertools.product pairs each seq index with every token id192 logits_to_penalize.extend(193 itertools.product(seqs_to_penalize, token_ids_to_penalize))194 195 start_idx += len(seq_ids)196 197 if logits_to_penalize:198 # use zip and * to group indices along each dimension199 # eg. [ (1,2), (1,3), (5,6) ] -> ( (1,1,5), (2,3,6) )200 logits[tuple(zip(*logits_to_penalize))] = -float("inf")201 202 # verifies that no rows in logits were missed unexpectedly203 assert start_idx == logits.shape[0]204 return logits205 206 207def _apply_penalties(logits: torch.Tensor, prompt_tokens_tensor: torch.Tensor,208 output_tokens_tensor: torch.Tensor,209 presence_penalties: torch.Tensor,210 frequency_penalties: torch.Tensor,211 repetition_penalties: torch.Tensor) -> torch.Tensor:212 num_seqs, vocab_size = logits.shape213 _, prompt_mask = _get_bin_counts_and_mask(prompt_tokens_tensor, vocab_size,214 num_seqs)215 output_bin_counts, output_mask = _get_bin_counts_and_mask(216 output_tokens_tensor, vocab_size, num_seqs)217 218 repetition_penalties = repetition_penalties[:, None].repeat(1, vocab_size)219 repetition_penalties[~(prompt_mask | output_mask)] = 1.0220 logits = torch.where(logits > 0, logits / repetition_penalties,221 logits * repetition_penalties)222 223 # We follow the definition in OpenAI API.224 # Refer to https://platform.openai.com/docs/api-reference/parameter-details225 logits -= frequency_penalties.unsqueeze_(dim=1) * output_bin_counts226 logits -= presence_penalties.unsqueeze_(dim=1) * output_mask227 return logits228 229 230def _apply_top_k_top_p(231 logits: torch.Tensor,232 p: torch.Tensor,233 k: torch.Tensor,234) -> torch.Tensor:235 logits_sort, logits_idx = logits.sort(dim=-1, descending=False)236 237 # Apply top-k.238 top_k_mask = logits_sort.size(1) - k.to(torch.long)239 # Get all the top_k values.240 top_k_mask = logits_sort.gather(1, top_k_mask.unsqueeze(dim=1))241 top_k_mask = logits_sort < top_k_mask242 logits_sort.masked_fill_(top_k_mask, -float("inf"))243 244 # Apply top-p.245 probs_sort = logits_sort.softmax(dim=-1)246 probs_sum = probs_sort.cumsum(dim=-1)247 top_p_mask = probs_sum <= 1 - p.unsqueeze(dim=1)248 # at least one249 top_p_mask[:, -1] = False250 logits_sort.masked_fill_(top_p_mask, -float("inf"))251 252 # Re-sort the probabilities.253 src = torch.arange(logits_idx.shape[-1],254 device=logits_idx.device).expand_as(logits_idx)255 logits_idx_inv = torch.empty_like(logits_idx).scatter_(dim=-1,256 index=logits_idx,257 src=src)258 logits = torch.gather(logits_sort, dim=-1, index=logits_idx_inv)259 return logits260 261 262def _apply_min_p(263 logits: torch.Tensor,264 min_p: torch.Tensor,265) -> torch.Tensor:266 """267 Adapted from268 https://github.com/oobabooga/text-generation-webui/blob/3146124ec01f02c8fb1650a6517cf1b60b537aaf/modules/sampler_hijack.py#L16C17-L16C17269 """270 probs = torch.softmax(logits, dim=-1)271 top_probs, _ = probs.max(dim=-1, keepdim=True)272 scaled_min_p = min_p.unsqueeze_(dim=1) * top_probs273 tokens_to_remove = probs < scaled_min_p274 logits = logits.masked_fill_(tokens_to_remove, -float("inf"))275 276 return logits277 278 279def _greedy_sample(280 selected_seq_groups: List[Tuple[List[int], SamplingParams]],281 samples: torch.Tensor,282) -> List[Tuple[List[int], List[int]]]:283 samples = samples.tolist()284 sample_idx = 0285 results = []286 for seq_group in selected_seq_groups:287 seq_ids, _ = seq_group288 num_parent_seqs = len(seq_ids)289 assert num_parent_seqs == 1, (290 "Greedy sampling should have only one seq.")291 parent_ids = list(range(num_parent_seqs))292 next_token_ids = [samples[sample_idx]]293 results.append((next_token_ids, parent_ids))294 sample_idx += num_parent_seqs295 return results296 297 298def _random_sample(299 selected_seq_groups: List[Tuple[List[int], SamplingParams]],300 is_prompts: List[bool],301 random_samples: torch.Tensor,302) -> List[Tuple[List[int], List[int]]]:303 # Find the maximum best_of value of the prompt phase requests.304 random_samples = random_samples.cpu()305 sample_idx = 0306 results = []307 for seq_group, is_prompt in zip(selected_seq_groups, is_prompts):308 seq_ids, sampling_params = seq_group309 num_parent_seqs = len(seq_ids)310 if is_prompt:311 # Prompt phase.312 parent_ids = [0] * sampling_params.best_of313 next_token_ids = random_samples[314 sample_idx, :sampling_params.best_of].tolist()315 else:316 # Generation phase.317 parent_ids = list(range(num_parent_seqs))318 next_token_ids = random_samples[sample_idx:sample_idx +319 num_parent_seqs, 0].tolist()320 results.append((next_token_ids, parent_ids))321 sample_idx += num_parent_seqs322 return results323 324 325def _beam_search_sample(326 selected_seq_groups: List[Tuple[List[int], SamplingParams]],327 is_prompts: List[bool],328 seq_data: Dict[int, SequenceData],329 logprobs: torch.Tensor,330) -> List[Tuple[List[int], List[int]]]:331 # We sample 2 * beam_width candidates to make sure that with high332 # probability we can get `beam_width` candidates in addition to333 # the finished sequences for the next iteration. See334 # https://github.com/tensorflow/tensor2tensor/blob/bafdc1b67730430d38d6ab802cbd51f9d053ba2e/tensor2tensor/utils/beam_search.py#L557-L563335 # for details. See also HF reference:336 # https://github.com/huggingface/transformers/blob/a4dd53d88e4852f023332d284ff07a01afcd5681/src/transformers/generation/utils.py#L3063-L3065337 #338 # NOTE: Beam search is not vectorized, so its speed can be slower than339 # other sampling methods.340 sample_idx = 0341 results = []342 for seq_group, is_prompt in zip(selected_seq_groups, is_prompts):343 seq_ids, sampling_params = seq_group344 num_parent_seqs = len(seq_ids)345 beam_width = sampling_params.best_of346 seq_group_logprobs = logprobs[sample_idx:sample_idx + num_parent_seqs]347 if is_prompt:348 # Prompt phase.349 assert num_parent_seqs == 1, (350 "Prompt input should have only one seq.")351 parent_ids = [0] * (2 * beam_width)352 _, next_token_ids = torch.topk(seq_group_logprobs[0],353 2 * beam_width)354 next_token_ids = next_token_ids.tolist()355 else:356 # Generation phase.357 cumulative_logprobs = [358 seq_data[seq_id].cumulative_logprob for seq_id in seq_ids359 ]360 cumulative_logprobs = torch.tensor(361 cumulative_logprobs,362 dtype=torch.float,363 device=seq_group_logprobs.device)364 seq_group_logprobs = (seq_group_logprobs +365 cumulative_logprobs.unsqueeze(dim=1))366 _, topk_ids = torch.topk(seq_group_logprobs.flatten(),367 2 * beam_width)368 topk_ids = topk_ids.tolist()369 vocab_size = seq_group_logprobs.size(-1)370 parent_ids = [i // vocab_size for i in topk_ids]371 next_token_ids = [i % vocab_size for i in topk_ids]372 results.append((next_token_ids, parent_ids))373 sample_idx += num_parent_seqs374 assert sample_idx == logprobs.size(0)375 return results376 377 378# torch.multinomial forces a GPU<->CPU sync.379# Therefore, we use an optimized implementation instead.380# Note that we always sample with replacement.381# probs will be modified in place, but this is fine, as we pass382# in a copy already.383def _multinomial(384 probs: torch.Tensor,385 num_samples: int,386 seq_groups: Optional[List[Tuple[List[int], SamplingParams]]] = None,387 generators: Optional[List[torch.Generator]] = None,388) -> torch.Tensor:389 if num_samples > 1:390 # This is equivalent to torch.repeat_interleaved (which also391 # forces a GPU<->CPU sync).392 # This allows us to do sampling with replacement by creating393 # num_samples copies of each row in the tensor, and then394 # batch sampling the resulting tensor.395 probs = probs[:, None, :].expand(probs.shape[0], num_samples,396 probs.shape[1]).contiguous().view(397 -1, probs.shape[1])398 q = torch.empty_like(probs)399 if seq_groups is None:400 q.exponential_()401 else:402 sample_idx = 0403 for (seq_ids, _), generator in zip(seq_groups, generators):404 next_sample_idx = sample_idx + len(seq_ids) * num_samples405 q[sample_idx:next_sample_idx].exponential_(generator=generator)406 sample_idx = next_sample_idx407 return probs.div_(q).argmax(dim=1).view(-1, num_samples)408 409 410def _sample_with_torch(411 probs: torch.Tensor,412 logprobs: torch.Tensor,413 sampling_metadata: SamplingMetadata,414 include_gpu_probs_tensor: bool,415 modify_greedy_probs: bool,416) -> Tuple[List[Tuple[List[int], List[int]]], Optional[torch.Tensor]]:417 categorized_seq_group_ids = {t: [] for t in SamplingType}418 categorized_sample_indices = sampling_metadata.categorized_sample_indices419 for i, seq_group in enumerate(sampling_metadata.seq_groups):420 _, sampling_params = seq_group421 sampling_type = sampling_params.sampling_type422 categorized_seq_group_ids[sampling_type].append(i)423 424 sample_results_dict: Dict[int, Tuple[List[int], List[int]]] = {}425 sample_metadata = {}426 multinomial_samples = {}427 428 # Create output tensor for sampled token ids.429 if include_gpu_probs_tensor:430 sampled_token_ids_tensor = torch.empty(logprobs.shape[0],431 1,432 dtype=torch.long,433 device=logprobs.device)434 else:435 sampled_token_ids_tensor = None436 437 # Counterintiutively, having two loops here is actually faster.438 # The first loop can run without waiting on GPU<->CPU sync.439 for sampling_type in SamplingType:440 sample_indices = categorized_sample_indices[sampling_type][:, 0]441 num_tokens = len(sample_indices)442 if num_tokens == 0:443 continue444 seq_group_ids = categorized_seq_group_ids[sampling_type]445 seq_groups = [sampling_metadata.seq_groups[i] for i in seq_group_ids]446 is_prompts = [i < sampling_metadata.num_prompts for i in seq_group_ids]447 sample_metadata[sampling_type] = (seq_group_ids, seq_groups,448 is_prompts, sample_indices)449 long_sample_indices = sample_indices.long()450 451 if sampling_type == SamplingType.GREEDY:452 greedy_samples = torch.argmax(logprobs[long_sample_indices],453 dim=-1)454 455 if include_gpu_probs_tensor:456 # Store sampled tokens in output tensor.457 sampled_token_ids_tensor[458 long_sample_indices] = greedy_samples.unsqueeze(-1)459 460 if modify_greedy_probs:461 # If required, modify the probabilities such that sampling from462 # the modified distribution would always sample the argmax463 # token id.464 _modify_greedy_probs_inplace(logprobs, probs,465 long_sample_indices,466 greedy_samples)467 468 elif sampling_type in (SamplingType.RANDOM, SamplingType.RANDOM_SEED):469 max_best_of_in_batch = 1470 for seq_group, is_prompt in zip(seq_groups, is_prompts):471 if is_prompt:472 _, sampling_params = seq_group473 max_best_of_in_batch = max(max_best_of_in_batch,474 sampling_params.best_of)475 seeded_args = {} if sampling_type == SamplingType.RANDOM else {476 "seq_groups": seq_groups,477 "generators": sampling_metadata.generators,478 }479 480 multinomial_samples[sampling_type] = _multinomial(481 probs[long_sample_indices], max_best_of_in_batch,482 **seeded_args)483 484 if include_gpu_probs_tensor:485 # Store sampled tokens in output tensor.486 sampled_token_ids_tensor[487 long_sample_indices] = multinomial_samples[sampling_type]488 489 elif sampling_type == SamplingType.BEAM:490 beam_search_logprobs = logprobs[sample_indices]491 else:492 raise ValueError(f"Unsupported sampling type: {sampling_type}")493 494 # GPU<->CPU sync happens in the loop below.495 # This also converts the sample output to Python objects.496 497 for sampling_type in SamplingType:498 if sampling_type not in sample_metadata:499 continue500 seq_group_ids, seq_groups, is_prompts, sample_indices = sample_metadata[501 sampling_type]502 if sampling_type == SamplingType.GREEDY:503 sample_results = _greedy_sample(seq_groups, greedy_samples)504 elif sampling_type in (SamplingType.RANDOM, SamplingType.RANDOM_SEED):505 sample_results = _random_sample(seq_groups, is_prompts,506 multinomial_samples[sampling_type])507 elif sampling_type == SamplingType.BEAM:508 sample_results = _beam_search_sample(seq_groups, is_prompts,509 sampling_metadata.seq_data,510 beam_search_logprobs)511 sample_results_dict.update(zip(seq_group_ids, sample_results))512 513 sample_results = [514 sample_results_dict[i]515 for i in range(len(sampling_metadata.seq_groups))516 ]517 return sample_results, sampled_token_ids_tensor518 519 520def _sample_with_triton_kernel(521 probs: torch.Tensor,522 logprobs: torch.Tensor,523 sampling_metadata: SamplingMetadata,524 sampling_tensors: SamplingTensors,525) -> List[Tuple[List[int], List[int]]]:526 categorized_seq_group_ids = {t: [] for t in SamplingType}527 categorized_sample_indices = sampling_metadata.categorized_sample_indices528 for i, seq_group in enumerate(sampling_metadata.seq_groups):529 _, sampling_params = seq_group530 sampling_type = sampling_params.sampling_type531 categorized_seq_group_ids[sampling_type].append(i)532 533 sample_results_dict: Dict[int, Tuple[List[int], List[int]]] = {}534 sample_metadata = {}535 max_best_of_in_batch = 1536 537 # Counterintiutively, having two loops here is actually faster.538 # The first loop can run without waiting on GPU<->CPU sync.539 for sampling_type in SamplingType:540 sample_indices = categorized_sample_indices[sampling_type][:, 0]541 sampled_token_indices = categorized_sample_indices[sampling_type][:, 1]542 num_tokens = len(sample_indices)543 if num_tokens == 0:544 continue545 seq_group_ids = categorized_seq_group_ids[sampling_type]546 seq_groups = [sampling_metadata.seq_groups[i] for i in seq_group_ids]547 is_prompts = [i < sampling_metadata.num_prompts for i in seq_group_ids]548 sample_metadata[sampling_type] = (seq_group_ids, seq_groups,549 is_prompts, sample_indices,550 sampled_token_indices)551 if sampling_type in (SamplingType.GREEDY, SamplingType.RANDOM,552 SamplingType.RANDOM_SEED):553 for seq_group, is_prompt in zip(seq_groups, is_prompts):554 if is_prompt:555 _, sampling_params = seq_group556 max_best_of_in_batch = max(max_best_of_in_batch,557 sampling_params.best_of)558 elif sampling_type == SamplingType.BEAM:559 beam_search_logprobs = logprobs[sample_indices]560 else:561 raise ValueError(f"Unsupported sampling type: {sampling_type}")562 563 sampled_tokens, _, _ = sample_triton(564 probs=probs,565 seeds=sampling_tensors.sampling_seeds,566 max_best_of=max_best_of_in_batch,567 sample_indices=sampling_tensors.sample_indices,568 logprobs=logprobs,569 # don't save logprobs because we have logic for that below570 # TODO: use this instead of the CPU-based logic below571 save_logprobs=False,572 )573 574 # GPU<->CPU sync happens in the loop below.575 576 for sampling_type in SamplingType:577 if sampling_type not in sample_metadata:578 continue579 (seq_group_ids, seq_groups, is_prompts, sample_indices,580 sampled_token_indices) = sample_metadata[sampling_type]581 if sampling_type == SamplingType.GREEDY:582 sample_results = _greedy_sample(583 seq_groups, sampled_tokens[sampled_token_indices][:, 0])584 elif sampling_type in (SamplingType.RANDOM, SamplingType.RANDOM_SEED):585 sample_results = _random_sample(586 seq_groups, is_prompts, sampled_tokens[sampled_token_indices])587 elif sampling_type == SamplingType.BEAM:588 sample_results = _beam_search_sample(seq_groups, is_prompts,589 sampling_metadata.seq_data,590 beam_search_logprobs)591 sample_results_dict.update(zip(seq_group_ids, sample_results))592 593 sample_results = [594 sample_results_dict[i]595 for i in range(len(sampling_metadata.seq_groups))596 ]597 return sample_results598 599 600def _sample(601 probs: torch.Tensor, logprobs: torch.Tensor,602 sampling_metadata: SamplingMetadata, sampling_tensors: SamplingTensors,603 include_gpu_probs_tensor: bool, modify_greedy_probs: bool604) -> Tuple[List[Tuple[List[int], List[int]]], Optional[torch.Tensor]]:605 return _sample_with_torch(606 probs,607 logprobs,608 sampling_metadata,609 include_gpu_probs_tensor=include_gpu_probs_tensor,610 modify_greedy_probs=modify_greedy_probs,611 )612 613 # TODO: Enable once Triton kernel & associated code is faster.614 # return _sample_with_triton_kernel(probs, logprobs, sampling_metadata,615 # sampling_tensors)616 617 618def _get_ranks(x: torch.Tensor, indices: torch.Tensor) -> torch.Tensor:619 """620 This function calculates the ranks of the chosen tokens in a logprob tensor.621 622 Args:623 x (torch.Tensor): 2D logprob tensor of shape (N, M)624 where N is the no. of tokens and M is the vocab dim.625 indices (torch.Tensor): List of chosen token indices.626 627 Returns:628 torch.Tensor: 1D tensor of shape (N,) where N is the no. of tokens.629 Each element in the returned tensor represents the rank 630 of the chosen token in the input logprob tensor.631 """632 vals = x[torch.arange(0, len(x), device=x.device, dtype=indices.dtype),633 indices]634 return (x > vals[:, None]).long().sum(1).add_(1)635 636 637def _get_logprobs(638 logprobs: torch.Tensor,639 sampling_metadata: SamplingMetadata,640 sample_results: List[Tuple[List[int], List[int]]],641) -> Tuple[List[Optional[List[Optional[Dict[int, float]]]]], List[List[Dict[642 int, float]]]]:643 # Prepare query indices644 batched_logprobs_query_seq_indices: List[int] = []645 batched_logprobs_query_token_indices: List[int] = []646 # at least get one logprob for each token647 largest_num_logprobs = 1648 sample_idx = 0649 for i, (seq_group, sample_result) in enumerate(650 zip(sampling_metadata.seq_groups, sample_results)):651 seq_ids, sampling_params = seq_group652 next_token_ids, parent_ids = sample_result653 num_parent_seqs = len(seq_ids)654 if (i < sampling_metadata.num_prompts655 and sampling_params.prompt_logprobs is not None):656 largest_num_logprobs = max(largest_num_logprobs,657 sampling_params.prompt_logprobs)658 prompt_len = sampling_metadata.prompt_lens[i]659 prompt_tokens = sampling_metadata.seq_data[660 seq_ids[0]].prompt_token_ids661 batched_logprobs_query_seq_indices.extend(662 sample_idx + j for j in range(prompt_len - 1))663 batched_logprobs_query_token_indices.extend(664 token_id for token_id in prompt_tokens[1:])665 sample_idx += prompt_len - 1666 batched_logprobs_query_seq_indices.extend(667 [sample_idx + parent_id for parent_id in parent_ids])668 batched_logprobs_query_token_indices.extend(next_token_ids)669 if sampling_params.logprobs is not None:670 largest_num_logprobs = max(largest_num_logprobs,671 sampling_params.logprobs)672 sample_idx += num_parent_seqs673 assert sample_idx == logprobs.size(0)674 675 batched_logprobs_query_seq_indices_gpu = torch.tensor(676 batched_logprobs_query_seq_indices, device=logprobs.device)677 batched_logprobs_query_token_indices_gpu = torch.tensor(678 batched_logprobs_query_token_indices, device=logprobs.device)679 680 # Batched query for logprobs of selected token681 batched_logprobs_query_result = logprobs[[682 batched_logprobs_query_seq_indices_gpu,683 batched_logprobs_query_token_indices_gpu684 ]]685 686 batched_ranks_query_result = _get_ranks(687 logprobs[batched_logprobs_query_seq_indices_gpu],688 batched_logprobs_query_token_indices_gpu)689 690 # Batched query for logprobs of topk tokens691 if largest_num_logprobs > 0:692 top_logprobs, top_token_ids = torch.topk(logprobs,693 largest_num_logprobs,694 dim=-1)695 top_logprobs = top_logprobs.cpu()696 top_token_ids = top_token_ids.cpu()697 else:698 top_logprobs, top_token_ids = None, None699 700 batched_logprobs_query_result = batched_logprobs_query_result.cpu()701 batched_ranks_query_result = batched_ranks_query_result.cpu()702 703 # Gather results704 result_prompt_logprobs: List[Optional[PromptLogprobs]] = []705 result_sample_logprobs: List[SampleLogprobs] = []706 sample_idx = 0707 query_result_idx = 0708 for i, (seq_group, sample_result) in enumerate(709 zip(sampling_metadata.seq_groups, sample_results)):710 seq_ids, sampling_params = seq_group711 next_token_ids, parent_ids = sample_result712 713 # Prompt logprobs714 if (i < sampling_metadata.num_prompts715 and sampling_params.prompt_logprobs is not None):716 num_logprobs = sampling_params.prompt_logprobs717 prompt_tokens = sampling_metadata.seq_data[718 seq_ids[0]].prompt_token_ids719 group_prompt_logprobs: PromptLogprobs = [None]720 for token_id in prompt_tokens[1:]:721 prompt_logprobs_dict = {722 token_id:723 (batched_logprobs_query_result[query_result_idx].item(),724 batched_ranks_query_result[query_result_idx].item())725 }726 if num_logprobs > 0:727 prompt_logprobs_dict.update(728 zip(729 top_token_ids[sample_idx, :num_logprobs].tolist(),730 zip(731 top_logprobs[732 sample_idx, :num_logprobs].tolist(),733 range(1, num_logprobs + 1))))734 group_prompt_logprobs.append({735 token_id: Logprob(*logprob_rank)736 for token_id, logprob_rank in prompt_logprobs_dict.items()737 })738 sample_idx += 1739 query_result_idx += 1740 result_prompt_logprobs.append(group_prompt_logprobs)741 else:742 result_prompt_logprobs.append(None)743 744 # Sample logprobs745 num_logprobs = sampling_params.logprobs746 if num_logprobs is None:747 num_logprobs = 0748 group_sample_logprobs: SampleLogprobs = []749 for next_token_id, parent_id in zip(next_token_ids, parent_ids):750 sample_logprobs_dict = {751 next_token_id:752 (batched_logprobs_query_result[query_result_idx].item(),753 batched_ranks_query_result[query_result_idx].item())754 }755 query_result_idx += 1756 if num_logprobs >= 0:757 sample_logprobs_dict.update(758 zip(759 top_token_ids[sample_idx +760 parent_id, :num_logprobs].tolist(),761 zip(762 top_logprobs[sample_idx +763 parent_id, :num_logprobs].tolist(),764 range(1, num_logprobs + 1))))765 group_sample_logprobs.append({766 token_id: Logprob(*logprob_rank)767 for token_id, logprob_rank in sample_logprobs_dict.items()768 })769 result_sample_logprobs.append(group_sample_logprobs)770 sample_idx += len(seq_ids)771 772 return result_prompt_logprobs, result_sample_logprobs773 774 775def _modify_greedy_probs_inplace(logprobs: torch.Tensor, probs: torch.Tensor,776 sample_indices: torch.Tensor,777 greedy_samples: torch.Tensor) -> None:778 """Modify the probability distributions of the greedily-sampled tokens such779 that each sampled token has a "probability" of 1.0. This is required by780 speculative decoding, which depends on the sampling method being encoded781 within the probability distribution for correctness.782 783 # Why do we only need to do this for greedy sampling?784 785 vLLM's sampler performs the following steps for greedy or multinomial786 (random) sampling:787 1. Get logits from model.788 2. Modify logits according to per-sequence sampling parameters.789 - Multiply by temperature, top-k and top-p masking, penalize tokens790 according to their frequency, etc.791 3. Sample a token.792 - Random sampling simply samples from the modified probability793 distribution.794 - Greedy sampling performs `argmax` to obtain the token with the795 highest likelihood.796 797 Ignoring greedy sampling for a moment, we find that the computed probability798 distribution has the following property: we can sample from it independently799 and find that the token sampled by the Sampler has a frequency corresponding800 to how often we see it in our sampling. In other words, for tokens sampled801 with vLLM's random SamplingType, the computed probability distribution802 encodes the sampling methodology completely.803 804 Greedy sampling does not normally have this property. vLLM modifies logits805 according to sampling params, then performs `argmax`, then returns the806 sampled token and the computed probability distribution. If we sample from807 the distribution, we'll find the likelihood of the greedily-sampled token808 is not always 1.0.809 810 Since lossless speculative decoding requires that the sampling methodology811 be encoded within the probability distribution, we are motivated to modify812 the probability distribution such that the sampled token has probability 1813 when speculative decoding is used.814 815 NOTE: Alternatively, we could use an extremely low temperature to achieve816 greedy sampling using multinomial computation and unite the codepaths. This817 has implications on the overall design of the sampler, e.g. how to record818 accurate logprobs for the user, so this improvement is deferred to later.819 """820 logprobs[sample_indices, :] = -float('inf')821 logprobs[sample_indices, greedy_samples] = 0.0822 probs[sample_indices, :] = 0823 probs[sample_indices, greedy_samples] = 1.0824 825 826def _build_sampler_output(827 sample_results: List[Tuple[List[int], List[int]]],828 sampling_metadata: SamplingMetadata,829 prompt_logprobs: List[Optional[PromptLogprobs]],830 sample_logprobs: List[SampleLogprobs],831 on_device_tensors: Optional[Tuple[torch.Tensor, torch.Tensor]],832) -> SamplerOutput:833 """Construct Python objects with the output of sampling.834 835 Args:836 on_device_tensors: Tuple containing on-device tensors with the837 probabilities used in sampling and the sampled token ids. This838 allows post-processing without copies to CPU/serialization, e.g. in839 speculative decoding rejection sampling.840 """841 842 sampler_output = []843 for (seq_group, sample_result, group_prompt_logprobs,844 group_sample_logprobs) in zip(sampling_metadata.seq_groups,845 sample_results, prompt_logprobs,846 sample_logprobs):847 seq_ids, _ = seq_group848 next_token_ids, parent_ids = sample_result849 seq_outputs = []850 for parent_id, next_token_id, logprobs in zip(parent_ids,851 next_token_ids,852 group_sample_logprobs):853 seq_outputs.append(854 SequenceOutput(seq_ids[parent_id], next_token_id, logprobs))855 sampler_output.append(856 SequenceGroupOutput(seq_outputs, group_prompt_logprobs))857 858 # If not specified, store None values in SamplerOutput.859 if on_device_tensors is not None:860 sampled_token_probs, sampled_token_ids = on_device_tensors861 else:862 sampled_token_probs, sampled_token_ids = (None, None)863 864 return SamplerOutput(865 outputs=sampler_output,866 sampled_token_probs=sampled_token_probs,867 sampled_token_ids=sampled_token_ids,868 )869 