-
Notifications
You must be signed in to change notification settings - Fork 2.6k
[TRTLLM-5670][feat] Support top log probabilities #7296
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3bb168e
e510ca0
0aeb5d6
5022c6b
9526f1d
51175be
a0c0412
6d31b22
98b472a
31bc186
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -147,6 +147,8 @@ def add_llm_args(parser): | |||||||||||||||||||||||||||||||||||||||
| default=False, | ||||||||||||||||||||||||||||||||||||||||
| action='store_true') | ||||||||||||||||||||||||||||||||||||||||
| parser.add_argument('--logprobs', default=False, action='store_true') | ||||||||||||||||||||||||||||||||||||||||
| parser.add_argument('--top_logprobs', type=int, default=None) | ||||||||||||||||||||||||||||||||||||||||
| parser.add_argument('--max_top_logprobs', type=int, default=0) | ||||||||||||||||||||||||||||||||||||||||
| return parser | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
@@ -213,6 +215,14 @@ def setup_llm(args, **kwargs): | |||||||||||||||||||||||||||||||||||||||
| batching_wait_iters=args.attention_dp_batching_wait_iters, | ||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # if only top_logprobs is set, set max_top_logprobs to top_logprobs | ||||||||||||||||||||||||||||||||||||||||
| if args.top_logprobs is not None and args.max_top_logprobs < args.top_logprobs: | ||||||||||||||||||||||||||||||||||||||||
| args.max_top_logprobs = args.top_logprobs | ||||||||||||||||||||||||||||||||||||||||
| # Remove this once torch sampler stops using enable_mixed_sampler | ||||||||||||||||||||||||||||||||||||||||
| is_greedy = (not args.max_beam_width > 1) and ( | ||||||||||||||||||||||||||||||||||||||||
| args.top_k is None or args.top_k == 1) and (args.top_p is None | ||||||||||||||||||||||||||||||||||||||||
| or args.top_p == 0.0) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+218
to
+225
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Validate inputs and enforce bounds. Guard against negative values; keep existing max sync-up. Apply: -# if only top_logprobs is set, set max_top_logprobs to top_logprobs
-if args.top_logprobs is not None and args.max_top_logprobs < args.top_logprobs:
- args.max_top_logprobs = args.top_logprobs
+# Validate and sync top_logprobs / max_top_logprobs
+if args.top_logprobs is not None and args.top_logprobs < 0:
+ raise ValueError("--top_logprobs must be >= 0")
+if args.max_top_logprobs is not None and args.max_top_logprobs < 0:
+ raise ValueError("--max_top_logprobs must be >= 0")
+if args.top_logprobs is not None and args.max_top_logprobs < args.top_logprobs:
+ args.max_top_logprobs = args.top_logprobs📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| llm = LLM( | ||||||||||||||||||||||||||||||||||||||||
| model=args.model_dir, | ||||||||||||||||||||||||||||||||||||||||
| backend='pytorch', | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -246,6 +256,8 @@ def setup_llm(args, **kwargs): | |||||||||||||||||||||||||||||||||||||||
| trust_remote_code=args.trust_remote_code, | ||||||||||||||||||||||||||||||||||||||||
| gather_generation_logits=args.return_generation_logits, | ||||||||||||||||||||||||||||||||||||||||
| max_beam_width=args.max_beam_width, | ||||||||||||||||||||||||||||||||||||||||
| max_top_logprobs=args.max_top_logprobs, | ||||||||||||||||||||||||||||||||||||||||
| enable_mixed_sampler=not is_greedy, | ||||||||||||||||||||||||||||||||||||||||
| **kwargs) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| use_beam_search = args.max_beam_width > 1 | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -257,14 +269,19 @@ def setup_llm(args, **kwargs): | |||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| assert best_of >= args.n, f"In sampling mode best_of value: {best_of} should be less or equal to n: {args.n}" | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| num_logprobs = args.logprobs | ||||||||||||||||||||||||||||||||||||||||
| if args.logprobs is not None: | ||||||||||||||||||||||||||||||||||||||||
| if args.top_logprobs is not None: | ||||||||||||||||||||||||||||||||||||||||
| num_logprobs = args.top_logprobs | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| sampling_params = SamplingParams( | ||||||||||||||||||||||||||||||||||||||||
| max_tokens=args.max_tokens, | ||||||||||||||||||||||||||||||||||||||||
| temperature=args.temperature, | ||||||||||||||||||||||||||||||||||||||||
| top_k=args.top_k, | ||||||||||||||||||||||||||||||||||||||||
| top_p=args.top_p, | ||||||||||||||||||||||||||||||||||||||||
| return_context_logits=args.return_context_logits, | ||||||||||||||||||||||||||||||||||||||||
| return_generation_logits=args.return_generation_logits, | ||||||||||||||||||||||||||||||||||||||||
| logprobs=args.logprobs, | ||||||||||||||||||||||||||||||||||||||||
| logprobs=num_logprobs, | ||||||||||||||||||||||||||||||||||||||||
| n=args.n, | ||||||||||||||||||||||||||||||||||||||||
| best_of=best_of, | ||||||||||||||||||||||||||||||||||||||||
| use_beam_search=use_beam_search) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -234,9 +234,9 @@ def _sample( | |||||||||||||||||||||||||
| logits_shape = logits.shape | ||||||||||||||||||||||||||
| logits = logits.view(-1, logits_shape[-1]) # sampling_batch expects 2D logits | ||||||||||||||||||||||||||
| if isinstance(sampling_params.top_k, int): | ||||||||||||||||||||||||||
| idx_next, probs = top_k_sampling_batch(logits, sampling_params.top_k) | ||||||||||||||||||||||||||
| idx_next, probs, _ = top_k_sampling_batch(logits, sampling_params.top_k) | ||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||
| idx_next, probs = greedy_search_sampling_batch(logits) | ||||||||||||||||||||||||||
| idx_next, probs, _ = greedy_search_sampling_batch(logits) | ||||||||||||||||||||||||||
| idx_next = idx_next.view(logits_shape[:-1]) | ||||||||||||||||||||||||||
|
Comment on lines
236
to
240
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Treat top_k ≤ 1 as greedy to avoid unnecessary top-k path. top_k == 1 is equivalent to greedy; avoid calling top-k sampling in that case. Apply: - if isinstance(sampling_params.top_k, int):
+ if isinstance(sampling_params.top_k, int) and sampling_params.top_k > 1:
idx_next, probs, _ = top_k_sampling_batch(logits, sampling_params.top_k)
else:
idx_next, probs, _ = greedy_search_sampling_batch(logits)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| return idx_next, probs | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -117,16 +117,21 @@ def set_exclude_last(self, should_exclude_last: bool) -> None: | |
| class LogProbStorage: | ||
| beam_width: int = -1 | ||
| log_probs: list[TokenLogprobs] | ||
| top_log_probs: list[TokenLogprobs] | ||
| cum_log_probs: list[float] | ||
|
|
||
| def _init(self, first_input: list[TokenLogprobs]): | ||
| self.beam_width = len(first_input) | ||
| self.log_probs = [[] for _ in range(self.beam_width)] | ||
| self.cum_log_probs = [0 for _ in range(self.beam_width)] | ||
|
|
||
| def append(self, | ||
| new_probs: list[TokenLogprobs], | ||
| cum_log_probs: Optional[list[float]] = None): | ||
| self.top_log_probs = [[] for _ in range(self.beam_width)] | ||
|
|
||
| def append( | ||
| self, | ||
| new_probs: list[TokenLogprobs], | ||
| cum_log_probs: Optional[list[float]] = None, | ||
| top_log_probs: Optional[list[TokenLogprobs]] = None, | ||
| ): | ||
| """ | ||
| new_probs: [beam_width, num_tokens] | ||
| cum_log_probs: [beam_width] | ||
|
|
@@ -137,14 +142,18 @@ def append(self, | |
| assert len(new_probs) == self.beam_width, "Beam width mismatch" | ||
| for beam_idx, probs in enumerate(new_probs): | ||
| self.log_probs[beam_idx].extend(probs) | ||
| if top_log_probs is not None: | ||
| self.top_log_probs[beam_idx].extend(top_log_probs[beam_idx]) | ||
| if cum_log_probs is not None: | ||
| self.cum_log_probs[beam_idx] = cum_log_probs[beam_idx] | ||
| else: | ||
| self.cum_log_probs[beam_idx] += sum( | ||
| next(iter(prob.values())).logprob for prob in probs) | ||
|
|
||
| def set_log_probs(self, log_probs: list[TokenLogprobs], | ||
| cum_log_probs: list[float]): | ||
| def set_log_probs(self, | ||
| log_probs: list[TokenLogprobs], | ||
| cum_log_probs: list[float], | ||
| top_log_probs: Optional[list[TokenLogprobs]] = None): | ||
| """ | ||
| Reset the storage and refill it with new values | ||
| log_probs: [beam_width, num_tokens] | ||
|
|
@@ -153,7 +162,7 @@ def set_log_probs(self, log_probs: list[TokenLogprobs], | |
| # reinitialize the storage to clear the lists | ||
| self._init(log_probs) | ||
| # append the new values | ||
| self.append(log_probs, cum_log_probs) | ||
| self.append(log_probs, cum_log_probs, top_log_probs) | ||
|
|
||
|
|
||
| class PyResult: | ||
|
|
@@ -187,23 +196,34 @@ def append_generation_logits(self, generation_logits: torch.Tensor): | |
|
|
||
| def append_log_probs(self, | ||
| log_probs: list[TokenLogprobs], | ||
| cum_log_probs: Optional[list[float]] = None): | ||
| cum_log_probs: Optional[list[float]] = None, | ||
| top_log_probs: Optional[list[TokenLogprobs]] = None): | ||
| """ | ||
| Append log_probs and optionally cum_log_probs and top_log_probs | ||
| log_probs: [beam_width, num_tokens] | ||
| cum_log_probs: [beam_width] | ||
| top_log_probs: [beam_width, num_tokens] | ||
| """ | ||
| if self._log_probs: | ||
| self._log_probs.append(log_probs, cum_log_probs) | ||
| self._log_probs.append(log_probs, cum_log_probs, top_log_probs) | ||
|
|
||
| def append_mm_embeddings(self, mm_embeddings: torch.Tensor): | ||
| self._mm_embeddings = SharedTensorContainer.from_tensor( | ||
| mm_embeddings).dump_to_dict() | ||
|
|
||
| def set_log_probs(self, log_probs: list[TokenLogprobs], | ||
| cum_log_probs: list[float]): | ||
| def set_log_probs(self, | ||
| log_probs: list[TokenLogprobs], | ||
| cum_log_probs: list[float], | ||
| top_log_probs: Optional[list[TokenLogprobs]] = None): | ||
| """ | ||
| Set log_probs and cum_log_probs to the new values | ||
| Set log_probs, cum_log_probs and top_log_probs to the new values | ||
| log_probs: [beam_width, num_tokens] | ||
| cum_log_probs: [beam_width] | ||
| top_log_probs: [beam_width, num_tokens] | ||
| """ | ||
| if self._log_probs: | ||
| self._log_probs.set_log_probs(log_probs, cum_log_probs) | ||
| self._log_probs.set_log_probs(log_probs, cum_log_probs, | ||
| top_log_probs) | ||
|
|
||
| @property | ||
| def context_logits(self) -> torch.Tensor | None: | ||
|
|
@@ -228,6 +248,10 @@ def generation_logits(self) -> torch.Tensor | None: | |
| def log_probs(self) -> list[TokenLogprobs] | None: | ||
| return self._log_probs and self._log_probs.log_probs | ||
|
|
||
| @property | ||
| def top_log_probs(self) -> list[TokenLogprobs] | None: | ||
| return self._log_probs and self._log_probs.top_log_probs | ||
|
|
||
| @property | ||
| def cum_log_probs(self) -> list[float] | None: | ||
| return self._log_probs and self._log_probs.cum_log_probs | ||
|
|
@@ -241,7 +265,7 @@ class LlmResult: | |
| """LlmResult wraps `bindings.executor.Result` but detour some features to Python implementation""" | ||
| py_result_properties = frozenset( | ||
| ('context_logits', 'generation_logits', 'log_probs', 'cum_log_probs', | ||
| 'mm_embedding_handle')) | ||
| 'top_log_probs', 'mm_embedding_handle')) | ||
|
|
||
| def __init__(self, | ||
| result: Union[bytes, tensorrt_llm.bindings.executor.Result], | ||
|
|
@@ -302,6 +326,7 @@ def __init__( | |
| is_draft: bool = False, | ||
| seq_slot: Optional[int] = None, | ||
| target_seq_slot: Optional[int] = None, | ||
| top_logprobs: Optional[int] = None, | ||
| **kwargs): | ||
|
Comment on lines
+329
to
330
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: top-logprobs dropped when
Apply: @@ class LlmRequest(...):
- self.py_return_log_probs = return_log_probs
+ self.py_return_log_probs = return_log_probs
self.py_top_logprobs = top_logprobs
@@
- self.py_result = PyResult(self.py_prompt_len, self.py_max_new_tokens,
- return_logits_device_memory, self.streaming,
- return_log_probs, return_context_logits,
- return_generation_logits,
- exclude_last_generation_logits)
+ self.py_result = PyResult(
+ self.py_prompt_len,
+ self.py_max_new_tokens,
+ return_logits_device_memory,
+ self.streaming,
+ # Allocate storage if either sampled-token logprobs OR top-K logprobs are requested.
+ (return_log_probs or self.calculate_top_logprobs),
+ return_context_logits,
+ return_generation_logits,
+ exclude_last_generation_logits,
+ )This ensures PyExecutor returns Also applies to: 373-375 |
||
|
|
||
| self.py_logits_post_processors = kwargs.pop("py_logits_post_processors", | ||
|
|
@@ -346,6 +371,7 @@ def __init__( | |
| TaskLayerModuleConfig] | None = None | ||
|
|
||
| self.py_return_log_probs = return_log_probs | ||
| self.py_top_logprobs = top_logprobs | ||
| self.py_return_context_logits = return_context_logits | ||
| self.py_return_generation_logits = return_generation_logits | ||
| self.py_return_logits_device_memory = return_logits_device_memory | ||
|
|
@@ -394,6 +420,10 @@ def create_response( | |
| def is_dummy(self): | ||
| return self.is_attention_dp_dummy or self.is_cuda_graph_dummy or self.is_dummy_request | ||
|
|
||
| @property | ||
| def calculate_top_logprobs(self): | ||
| return self.py_top_logprobs is not None and self.py_top_logprobs > 0 | ||
|
|
||
| def finish_by(self, reason: FinishReason, beam: int) -> None: | ||
| """CPP finish by reason does not support beam_width > 1""" | ||
| self.state = LlmRequestState.GENERATION_COMPLETE | ||
|
|
@@ -564,7 +594,8 @@ def executor_request_to_llm_request( | |
| cache_salt_id=executor_request.cache_salt_id, | ||
| arrival_time=getattr(executor_request, "py_arrival_time", None), | ||
| py_multimodal_data=getattr(executor_request, "py_multimodal_data", | ||
| None)) | ||
| None), | ||
| top_logprobs=executor_request.output_config.top_logprobs) | ||
| if child_req_ids: | ||
| for child_id in child_req_ids: | ||
| llm_request.create_child_request(child_id) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Constructor overload: OK; ensure binding/serialization updated.
API surface extension is fine. Please verify pybind and serialization paths include
topLogProbs.🏁 Script executed:
Length of output: 46
🏁 Script executed:
Length of output: 4285
Include topLogProbs in serialization and Python bindings
su::serialize(config.topLogProbs, os)betweenreturnContextLogitsandadditionalModelOutputs, and in deserializeOutputConfig insertauto topLogProbs = su::deserialize<std::optional<SizeType32>>(is)so it’s passed into the returned OutputConfig.topLogProbsproperty in the pybind11class_<OutputConfig>block to expose it on the Python side.🤖 Prompt for AI Agents