Prhub

#43260 [Frontend] Add truncation side to OpenAI endpoints

原始 PR 作者 ruizhang99 合并时间 2026-05-22 11:32 文件变更 5 提交数 7 评论 8 代码增减 +147 / -6

执行摘要

为 OpenAI 端点添加截断方向控制

当使用 prompt 截断时,不同场景需要保留 prompt 头部(如系统指令)或尾部(如最新对话)。此参数提供灵活选择,避免用户手动切片 token 序列。

这是一个设计清晰的特性 PR,改动量小,覆盖了完整的参数传递与验证链路,值得前端开发者作为新增 endpoint 参数的参考模板。

讨论亮点

Reviewer DarkLight1337 指出作者最初对 vllm/inputs/preprocess.py 的改动应避免,因为该代码路径已废弃,并将于 PR #38688 中移除。作者接受建议并回退了该文件变更。其余评论无实质讨论。

实现拆解

  1. 协议层添加字段:在 ChatCompletionRequest(vllm/entrypoints/openai/chat_completion/protocol.py)和 CompletionRequest(vllm/entrypoints/openai/completion/protocol.py)中新增 truncation_side: Literal["left", "right"] | None 字段,并传入 build_tok_params 方法。
  2. 渲染参数验证与传播:在 vllm/renderers/params.py 的 TokenizeParams 中,__post_init__ 增加值合法性校验(仅允许 None、"left"、"right");with_kwargs 方法从 kwargs 中弹出 truncation_side 并传递给新建的 TokenizeParams。
  3. tokenization 核心调整:在 get_encode_kwargs 中,当同时指定 truncation_side 和 truncate_prompt_tokens 时,禁用 tokenizer 原生截断(truncation=False),改为由 vLLM 内部函数 _token_truncation 根据方向切片,避免 tokenizer 默认截断方向干扰。
  4. 测试覆盖:新增 test_serving_chat_truncation_side_controls_prompt_truncation(tests/entrypoints/openai/chat_completion/test_serving_chat.py)和 test_completion_truncation_side_controls_prompt_truncation(tests/entrypoints/openai/completion/test_completion.py),分别验证 chat 和 completion 端点下 right/left 截断结果正确。
文件 模块 状态 重要度
vllm/renderers/params.py 渲染器 modified 6.5
vllm/entrypoints/openai/chat_completion/protocol.py 入口层 modified 5.52
vllm/entrypoints/openai/completion/protocol.py 入口层 modified 5.52
tests/entrypoints/openai/chat_completion/test_serving_chat.py 对话服务 modified 6.55
tests/entrypoints/openai/completion/test_completion.py 补全服务 modified 5.83

关键符号

test_serving_chat_truncation_side_controls_prompt_truncation _render_chat_prompt_token_ids test_completion_truncation_side_controls_prompt_truncation __post_init__ with_kwargs get_encode_kwargs build_tok_params

关键源码片段

vllm/renderers/params.py core-logic

核心渲染参数类,包含 truncation_side 验证、传播以及 get_encode_kwargs 中禁用 tokenizer 截断的关键逻辑。

def get_encode_kwargs(self) -> dict[str, Any]:
    """The arguments to pass to `tokenizer.encode`."""
    # Allow one extra token so truncation looks correct
    # while still failing `self._token_len_check`.
    max_length = self.max_input_tokens + 1
​
    # Explicit truncation-side overrides require the full token sequence
    # so we can slice from the requested side in `_token_truncation`.
    # Disable tokenizer-level truncation because generation tokenizers
    # default to left truncation while callers may request right truncation.
    if self.truncation_side is not None and self.truncate_prompt_tokens is not None:
        return dict(
            truncation=False, # disable tokenizer truncation
            add_special_tokens=self.add_special_tokens,
            max_length=None,
            padding=False,
        )
​
    # Otherwise, allow tokenizer truncation up to max_length
    # or explicit truncate_prompt_tokens if set.
    return dict(
        truncation=max_length if self.truncate_prompt_tokens is None
                   else self.truncate_prompt_tokens,
        add_special_tokens=self.add_special_tokens,
        max_length=max_length,
        padding=False,
    )
vllm/entrypoints/openai/chat_completion/protocol.py core-logic

ChatCompletionRequest 类添加 truncation_side 字段,并在 build_tok_params 中传递至 TokenizeParams。

class ChatCompletionRequest(OpenAIBaseModel):
    # ... existing fields ...
    truncation_side: Literal["left", "right"] | None = Field(
        default=None,
        description=(
            "Which side to truncate from when truncate_prompt_tokens is active. "
            "'right' keeps the first N tokens. "
            "'left' keeps the last N tokens."
        ),
    )
    # ... other fields ...
​
    def build_tok_params(self, model_config: ModelConfig) -> TokenizeParams:
        # ... existing logic ...
        return TokenizeParams(
            max_total_tokens=model_config.max_model_len,
            max_output_tokens=max_output_tokens or 0,
            truncate_prompt_tokens=self.truncate_prompt_tokens,
            truncation_side=self.truncation_side, # newly passed
            add_special_tokens=self.add_special_tokens,
            needs_detokenization=bool(self.echo and not self.return_token_ids),
            max_total_tokens_param="max_model_len",
            max_output_tokens_param=max_output_tokens_param,
        )

评论区精华

预处理器废弃路径变更 设计

DarkLight1337 指出 vllm/inputs/preprocess.py 的改动应避免,该路径已废弃,将在 #38688 移除。

结论:作者接受建议并回退了该文件变更。 · 已解决

风险与影响

主要风险在于 tokenization 逻辑调整:当显式指定 truncation_side 时,禁用 tokenizer 级截断可能与其他参数(如 add_special_tokens)产生细微交互。另外,tokenizers 库本身的默认截断方向可能是左或右,此改动统一交由 vLLM 处理,在 edge case 下可能产生与之前不同的 token 序列,但仅当用户主动设置 truncation_side 时生效,故向后兼容。

用户影响:新增可选参数,默认行为不变,用户通过设置 truncation_side 获得截断方向控制。系统影响:无性能、安全风险。团队影响:无。

参数向后兼容 tokenizer 截断方向覆盖

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论