Repository navigation
[https://nvbugs/6577736][perf] do not materialize embedding bias rows on host #19412
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
base: release/1.3
Are you sure you want to change the base?
Changes from all commits
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 |
|---|---|---|
|
|
@@ -18,7 +18,7 @@ | |
| from tensorrt_llm.executor.result import SimpleTokenLogprobs, TokenLogprobs | ||
| from tensorrt_llm.inputs.multimodal import strip_mm_encoder_inputs | ||
| from tensorrt_llm.inputs.registry import get_multimodal_encoder_item_metadata | ||
| from tensorrt_llm.sampling_params import LogprobMode | ||
| from tensorrt_llm.sampling_params import EmbeddingBias, LogprobMode | ||
|
|
||
| SamplingConfig = tllm_executor.SamplingConfig | ||
|
|
||
|
|
@@ -907,6 +907,7 @@ def __init__( | |
| logits_chunk_size: int = 8, | ||
| logprobs_mode: LogprobMode = LogprobMode.RAW, | ||
| logprobs_simple_format: bool = False, | ||
| embedding_bias: Optional[EmbeddingBias] = None, | ||
| return_routed_experts: bool = False, | ||
| **kwargs): | ||
| self.py_sampling_strategy: "Strategy | None" = None | ||
|
|
@@ -915,8 +916,7 @@ def __init__( | |
| None) | ||
| # Owned entirely on the Python side: the TorchSampler is the only | ||
| # consumer, so the C++ request no longer carries a copy. | ||
| self.py_embedding_bias: Optional[torch.Tensor] = kwargs.pop( | ||
| "embedding_bias", None) | ||
| self.py_embedding_bias: EmbeddingBias | None = embedding_bias | ||
| self.py_lora_path: str | None = kwargs.pop("py_lora_path", None) | ||
| # Multimodal data | ||
| self.py_multimodal_data = kwargs.pop("py_multimodal_data", None) | ||
|
|
@@ -1577,7 +1577,7 @@ def executor_request_to_llm_request( | |
| sampling_config=sampling_config, | ||
| is_streaming=executor_request.streaming, | ||
| end_id=executor_request.end_id, | ||
| embedding_bias=executor_request.embedding_bias, | ||
| embedding_bias=getattr(executor_request, "py_embedding_bias", None), | ||
|
Collaborator
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. Direct executor.Request callers still populate the public embedding_bias field, but this conversion now reads only py_embedding_bias. Their bias is silently discarded. Fall back to normalizing the existing public tensor field. |
||
| stop_words_list=stop_words_list, | ||
| position_ids=position_ids, | ||
| prompt_embedding_table=None if executor_request.prompt_tuning_config | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -480,6 +480,7 @@ def __init__( | |
| # related modules | ||
| self.resource_manager = resource_manager | ||
| self.model_engine = model_engine | ||
| self.vocab_size: int = self.model_engine.model.model_config.pretrained_config.vocab_size | ||
| self._enable_adp_dummy_fixes = getattr(model_engine, | ||
| "_enable_adp_dummy_fixes", False) | ||
| self._enable_scheduler_aware_adp_dummy = getattr( | ||
|
|
@@ -5625,6 +5626,14 @@ def _validate_request_budget(self, request: LlmRequest) -> None: | |
| f"max_new_tokens={request.max_new_tokens}, " | ||
| f"beam_width={request.py_beam_width}).") | ||
|
|
||
| def _validate_embedding_bias(self, request: LlmRequest) -> None: | ||
| embedding_bias = request.py_embedding_bias | ||
| if embedding_bias is not None: | ||
| for idx, val in embedding_bias: | ||
| if idx < 0 or idx >= self.vocab_size: | ||
| raise ValueError( | ||
| "Embedding bias index must be in [0, vocab_size - 1]") | ||
|
Collaborator
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. Nit: could the error include the offending index and the range, e.g. |
||
|
|
||
| def _validate_request(self, request: LlmRequest): | ||
| # Validate context-side pipelined-transfer constraints. | ||
| disagg_params = request.py_disaggregated_params | ||
|
|
@@ -5706,6 +5715,8 @@ def _validate_request(self, request: LlmRequest): | |
| # Check token ID ranges | ||
| self._validate_token_id_range(request) | ||
|
|
||
| self._validate_embedding_bias(request) | ||
|
|
||
| # Perform sampler-specific validation | ||
| self.sampler.validate_request(request) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,7 +40,8 @@ | |
| from ..llmapi.tracer import global_tracer | ||
| from ..llmapi.utils import _SyncQueue, configure_cpu_affinity, logger_debug | ||
| from ..runtime import ModelConfig | ||
| from ..sampling_params import BatchedLogitsProcessor, SamplingParams | ||
| from ..sampling_params import (BatchedLogitsProcessor, EmbeddingBias, | ||
| SamplingParams) | ||
| from .executor import GenerationExecutor, IterationResultQueue | ||
| from .ipc import FusedIpcQueue, IpcQueue | ||
| from .postproc_worker import (PostprocParams, PostprocWorker, | ||
|
|
@@ -338,6 +339,24 @@ def _load_prompt_adapter(self, | |
| model_config=self._runtime_model_config, | ||
| uids=[str(prompt_adapter_request.adapter_id)]) | ||
|
|
||
| @staticmethod | ||
| def _embedding_bias_sparse_to_dense(embedding_bias: EmbeddingBias, *, | ||
| vocab_size: int) -> torch.Tensor: | ||
| return torch.zeros( | ||
| vocab_size, | ||
| dtype=torch.float32, | ||
| ).scatter_( | ||
| dim=0, | ||
| index=torch.tensor( | ||
| [idx for (idx, val) in embedding_bias], | ||
| dtype=torch.int32, | ||
| ), | ||
| src=torch.tensor( | ||
| [val for (idx, val) in embedding_bias], | ||
| dtype=torch.float32, | ||
| ), | ||
| ) | ||
|
|
||
| def _enqueue_request(self, | ||
| request: GenerationRequest, | ||
| result_wait_queue=None) -> int: | ||
|
|
@@ -490,7 +509,13 @@ def _deduce_max_tokens(request: GenerationRequest) -> int: | |
| bad_words=request.sampling_params._get_bad_words(), | ||
| stop_words=[] if request.sampling_params.ignore_eos else | ||
| request.sampling_params._get_stop_words(), | ||
| embedding_bias=request.sampling_params.embedding_bias, | ||
| embedding_bias=( | ||
| None if request.sampling_params._embedding_bias_sparse | ||
| is None or self._is_pytorch_backend else | ||
| self._embedding_bias_sparse_to_dense( | ||
| request.sampling_params._embedding_bias_sparse, | ||
| vocab_size=self._runtime_model_config.vocab_size, | ||
| )), | ||
| lora_config=lora_config, | ||
| prompt_tuning_config=prompt_tuning_config, | ||
| multimodal_input=multimodal_input, | ||
|
|
@@ -512,6 +537,7 @@ def _deduce_max_tokens(request: GenerationRequest) -> int: | |
| request.sampling_params.logprobs_simple_format) | ||
| executor_request.py_return_routed_experts = ( | ||
| request.sampling_params.return_routed_experts) | ||
| executor_request.py_embedding_bias = request.sampling_params._embedding_bias_sparse | ||
|
Collaborator
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. Moving bias into py_embedding_bias drops it during request broadcasting because the Python attribute whitelist excludes this field. Pipeline-parallel sampling consequently ignores logit bias. Include it in serialization to every rank. |
||
|
|
||
| # here we add executor_request.py_disaggregated_params= request.disaggregated_params for python cache transceiver | ||
| if self._is_pytorch_backend and request.disaggregated_params is not None: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,7 +17,7 @@ | |
| from abc import ABC, abstractmethod | ||
| from collections.abc import Iterable, Mapping | ||
| from dataclasses import dataclass, field, fields | ||
| from typing import Any, List, NamedTuple, Optional, Tuple, Union | ||
| from typing import Any, List, NamedTuple, Optional, Tuple, TypeAlias, Union | ||
|
|
||
| import torch | ||
| from pydantic import BaseModel | ||
|
|
@@ -182,6 +182,9 @@ def __call__( | |
| pass # noqa | ||
|
|
||
|
|
||
| EmbeddingBias: TypeAlias = tuple[tuple[int, float], ...] | ||
|
|
||
|
|
||
| @dataclass(slots=True, kw_only=True) | ||
| class SamplingParams: | ||
| """Sampling parameters for text generation. | ||
|
|
@@ -204,7 +207,10 @@ class SamplingParams: | |
| stop (str, List[str], optional): A string or a list of strings that stop the generation when they are generated. The returned output will not contain the stop strings unless include_stop_str_in_output is True. Defaults to None. | ||
| stop_token_ids (List[int], optional): A list of token ids that stop the generation when they are generated. Defaults to None. | ||
| include_stop_str_in_output (bool): Whether to include the stop strings in output text. Defaults to False. | ||
| embedding_bias (torch.Tensor, optional): The embedding bias tensor. Expected type is kFP32 and shape is [vocab_size]. Defaults to None. | ||
| embedding_bias (EmbeddingBias, torch.Tensor, List[float], optional): The embedding bias tensor in dense or sparse representation. | ||
| Dense representations can be torch.Tensor (float32 dtype, shape (vocab_size,)) or list of length vocab_size. | ||
| Sparse representation is a tuple of (vocabulary index, bias value) pairs. The behavior for repeated indices is undefined. | ||
| Defaults to None. | ||
| logits_processor (tensorrt_llm.sampling_params.LogitsProcessor, List[tensorrt_llm.sampling_params.LogitsProcessor], optional): The logits postprocessor callback(s). Defaults to None. | ||
| If a list, each processor is applied in order during generation (supported in PyTorch backend only). | ||
| apply_batched_logits_processor (bool): Whether to apply batched logits postprocessor callback. Defaults to False. | ||
|
|
@@ -291,7 +297,8 @@ class SamplingParams: | |
| include_stop_str_in_output: bool = False | ||
| _stop_word_ids: Optional[List[List[int]]] = field(default=None, init=False, repr=False) | ||
|
|
||
| embedding_bias: Optional[torch.Tensor] = None | ||
| embedding_bias: Optional[Union[torch.Tensor, list[float], EmbeddingBias]] = None | ||
| _embedding_bias_sparse: Optional[EmbeddingBias] = field(default=None, init=False, repr=False) | ||
| logits_processor: Optional[Union[LogitsProcessor, List[LogitsProcessor]]] = None | ||
| apply_batched_logits_processor: bool = False | ||
|
|
||
|
|
@@ -373,10 +380,18 @@ def __post_init__(self): | |
| self.best_of = self.best_of or self.n | ||
|
|
||
| if self.embedding_bias is not None: | ||
| if isinstance(self.embedding_bias, torch.Tensor): | ||
| self.embedding_bias = self.embedding_bias.detach().clone() | ||
| if isinstance(self.embedding_bias, (torch.Tensor, list)): | ||
|
Collaborator
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. On the PyTorch path the C++ |
||
| if isinstance(self.embedding_bias, torch.Tensor): | ||
| embedding_bias_list = self.embedding_bias.tolist() | ||
| else: | ||
| embedding_bias_list = self.embedding_bias | ||
| self._embedding_bias_sparse = tuple( | ||
|
Collaborator
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. The sparse cache is initialized only once, although SamplingParams remains mutable. Setting or clearing embedding_bias afterward leaves requests using stale cached values. Refresh the cache when preparing each request.
Collaborator
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. Short dense biases are now silently zero-padded (as the test comment notes); before, they failed in the sampler on the shape mismatch, and the gRPC path still enforces |
||
| [(idx, val) for idx, val in enumerate(embedding_bias_list) if val != 0] | ||
| ) | ||
| else: | ||
| self.embedding_bias = torch.tensor(self.embedding_bias, dtype=torch.float32) | ||
| self._embedding_bias_sparse = self.embedding_bias | ||
| else: | ||
| self._embedding_bias_sparse = None | ||
|
|
||
| self._validate() | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,10 +56,10 @@ | |
| from tensorrt_llm.inputs.media_io import MediaModality | ||
| from tensorrt_llm.llmapi import ConversationParams as LlmConversationParams | ||
| from tensorrt_llm.llmapi import DisaggregatedParams as LlmDisaggregatedParams | ||
| from tensorrt_llm.llmapi import (DisaggScheduleStyle, GuidedDecodingParams, | ||
| SamplingParams) | ||
| from tensorrt_llm.llmapi import DisaggScheduleStyle, GuidedDecodingParams | ||
| from tensorrt_llm.llmapi.reasoning_parser import ReasoningParserFactory | ||
| from tensorrt_llm.sampling_params import (check_logprobs_limit, | ||
| from tensorrt_llm.sampling_params import (EmbeddingBias, SamplingParams, | ||
| check_logprobs_limit, | ||
| validate_thinking_token_budget) | ||
| from tensorrt_llm.scheduling_params import AgentHierarchy | ||
| from tensorrt_llm.visual_gen.params import MediaRole | ||
|
|
@@ -98,8 +98,8 @@ def ensure_request_chat_template_allowed(request: Any, | |
|
|
||
| def _logit_bias_to_embedding_bias( | ||
| logit_bias: Optional[Dict[str, float]], | ||
| vocab_size: Optional[int]) -> Optional[torch.Tensor]: | ||
| """Convert OpenAI logit_bias dict to embedding_bias tensor for sampling.""" | ||
| vocab_size: Optional[int]) -> Optional[EmbeddingBias]: | ||
| """Convert OpenAI logit_bias dict to sparse embedding_bias for sampling.""" | ||
| if logit_bias is None: | ||
| return None | ||
| if vocab_size is None: | ||
|
|
@@ -112,7 +112,7 @@ def _logit_bias_to_embedding_bias( | |
| raise ValueError("vocab_size must be positive when logit_bias is used") | ||
|
|
||
| # Create 1D zeros tensor as expected by executor API (will be unsqueezed to [1, vocab_size] internally) | ||
| embedding_bias = torch.zeros(vocab_size, dtype=torch.float32) | ||
| embedding_bias_list = [] | ||
|
|
||
| # Apply biases for specified token IDs | ||
| for token_str, bias in logit_bias.items(): | ||
|
|
@@ -141,9 +141,10 @@ def _logit_bias_to_embedding_bias( | |
| raise ValueError( | ||
| f"logit_bias value for token ID {token_id} must be in " | ||
| f"[{_LOGIT_BIAS_MIN:g}, {_LOGIT_BIAS_MAX:g}]") | ||
| embedding_bias[token_id] = bias_value | ||
| embedding_bias_list.append((token_id, bias_value)) | ||
|
Collaborator
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. Keys like |
||
|
|
||
| return embedding_bias | ||
| return tuple( | ||
| sorted(embedding_bias_list, key=lambda idx_and_val: idx_and_val[0])) | ||
|
|
||
|
|
||
| class OpenAIBaseModel(BaseModel): | ||
|
|
||
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.
Hi @ixlmar, thanks for the embedding-bias perf work!
Quick heads-up on timing: release/1.3 is only taking fixes for 1.3.0 blockers at this point, and the linked bug isn't marked for 1.3.0. Could we hold the merge on this branch for now?
If it's needed in 1.3.0, just tag the bug for 1.3.0 and resolve this thread.
Only about release timing, not the code; please hold the merge for now, your call on whether it goes in.