# PR #54692 完整报告

- 仓库：`vllm-project/vllm`
- 标题：[Bugfix][Frontend] Preserve token offset origins after left text pre-trimming
- 合并时间：2026-09-01 16:02
- 原文链接：http://prhub.com.cn/vllm-project/vllm/pull/54692

---

# 执行摘要

- 一句话：修复左截断场景下 token 偏移量坐标原点未恢复的静默数据错误。
- 推荐动作：此 PR 值得精读，特别是对前端渲染器或 token 化流程有责任的工程师。它展示了一个隐蔽的、静默的数据损坏 bug 如何被精确地识别和修复，修复方案小而精巧，测试覆盖到位。其设计决策（计算与应用分离、保留特殊 token 偏移量）值得参考。

# 功能与动机

当请求显式设置 `truncation_side` 时，vLLM 会禁用 tokenizer 级别截断，改为先通过 `_text_len_check` 进行字符级预截断，再进行 token 化。预截断后，fast tokenizer 返回的偏移量是相对于截断后字符串的。但 vLLM 未将这些偏移量重新映射回原始坐标就直接返回，导致偏移量指向错误字符。这是一个静默的数据损坏问题，因为偏移量数量与 token ID 数量匹配，调用者可能不会发现错误。该问题可通过公共 GPU-less render server 暴露。

# 实现拆解

1. **计算左截断偏移量**: 在 `vllm/renderers/params.py` 的 `TokenizeParams` 类中新增 `_get_text_truncation_offset` 方法。该方法复用 `_text_len_check` 中的计算逻辑（`max_input_chars = max_input_tokens * tokenizer.max_chars_per_token`），但仅计算并返回左截断时被移除的源字符串前缀长度，即 `max(len(text) - max_input_chars, 0)`。
2. **应用偏移量调整**: 在 `vllm/renderers/base.py` 的 `BaseRenderer` 类中新增静态方法 `_apply_prompt_char_offset`。该方法遍历 `prompt_token_offsets`，将每个偏移量 `(start, end)` 加上预截断偏移量 `char_offset`，但保留特殊 token 的 `(0, 0)` 偏移量不变。
3. **集成至核心流程**: 修改 `_tokenize_singleton_prompt` 和 `_tokenize_singleton_prompt_async` 方法。在调用 `apply_pre_tokenization`（该方法内部调用 `_text_len_check` 进行预截断）之前，先调用 `_get_text_truncation_offset` 计算偏移量。在完成 token 化后、调用 `apply_post_tokenization` 之前，调用 `_apply_prompt_char_offset` 将偏移量映射回原始坐标。
4. **配套测试**: 在 `tests/renderers/test_token_offsets.py` 中新增 `_OffsetTokenizer`（一个确定性的快速 tokenizer mock）以及两个测试用例 `test_text_pretrim_preserves_source_offsets` 和 `test_async_left_text_pretrim_preserves_source_offsets`，分别验证同步和异步路径下，左 / 右截断后偏移量的正确性。

关键文件：
- `vllm/renderers/base.py`（模块 渲染器；类别 source；类型 core-logic；符号 _apply_prompt_char_offset）: 核心逻辑变更：新增 `_apply_prompt_char_offset` 静态方法，并在 `_tokenize_singleton_prompt` 和 `_tokenize_singleton_prompt_async` 中集成偏移量恢复逻辑，是修复的执行点。
- `vllm/renderers/params.py`（模块 渲染器；类别 source；类型 core-logic；符号 _get_text_truncation_offset）: 核心逻辑变更：新增 `_get_text_truncation_offset` 方法，负责计算预截断移除的字符数，是修复的计算源。
- `tests/renderers/test_token_offsets.py`（模块 偏移量测试；类别 test；类型 test-coverage；符号 _OffsetTokenizer, __call__, test_text_pretrim_preserves_source_offsets, test_async_left_text_pretrim_preserves_source_offsets）: 测试覆盖：新增确定性 mock tokenizer `_OffsetTokenizer` 和两个测试用例，直接验证修复前后偏移量的正确性。

关键符号：_get_text_truncation_offset, _apply_prompt_char_offset, _tokenize_singleton_prompt, _tokenize_singleton_prompt_async

## 关键源码片段

### `vllm/renderers/base.py`

核心逻辑变更：新增 `_apply_prompt_char_offset` 静态方法，并在 `_tokenize_singleton_prompt` 和 `_tokenize_singleton_prompt_async` 中集成偏移量恢复逻辑，是修复的执行点。

```python
@staticmethod
def _apply_prompt_char_offset(
    prompt: TokensPrompt, char_offset: int
) -> TokensPrompt:
    """Map token offsets back to the original prompt after a text pre-trim.

    When _text_len_check pre-truncates the prompt text from the left,
    the fast tokenizer's offset_mapping becomes relative to the trimmed
    suffix. This method adds the length of the removed prefix back to
    each offset, restoring the original coordinate origin.
    """
    if char_offset == 0:
        return prompt

    offsets = prompt.get("prompt_token_offsets")
    if offsets is not None:
        prompt["prompt_token_offsets"] = [
            # Fast tokenizers use (0, 0) for special tokens, which are
            # not character spans in the source prompt and should remain
            # unchanged.
            (start, end)
            if (start, end) == (0, 0)
            else (start + char_offset, end + char_offset)
            for start, end in offsets
        ]

    return prompt

```

# 评论区精华

本 PR 的 review 讨论较少，主要为维护者的 approve 和 CI 触发。核心讨论体现在 PR body 和关联 Issue (#54407) 中，明确了问题的精确边界（字符级预截断 vs token 级别截断）和解决方案的独立性（本 PR 恢复坐标原点，PR#54407 对齐 token 级截断）。解决方案被接受后，未有进一步的技术争议。

- 暂无高价值评论线程

# 风险与影响

- 风险：风险极低。变更仅影响返回给调用者的元数据 (`prompt_token_offsets`)，不触及模型输入 (`prompt_token_ids`)、模型输出、权重或采样逻辑，因此不影响模型准确性。修复逻辑是直接的算术偏移，且与现有测试（`test_explicit_truncation_side_truncates_offsets`）协同工作，新测试用例专门覆盖了修复前失败的场景。唯一的潜在风险是计算偏移量时复用的 `_text_len_check` 逻辑未来可能被修改，但两处代码紧邻，易于维护。
- 影响：
 1. **用户**: 修复了公共 API 返回的 `prompt_token_offsets` 字段在特定截断组合下的数据错误，提升了 API 输出数据的可靠性。对于依赖偏移量进行文本定位的消费者（如前端调试、上下文分析），此修复至关重要。
 2. **系统**: 无系统性影响。变更局限在前端渲染器的 token 化流程内。
 3. **团队**: 为 token 化流程中的多层截断（字符级预截断 + token 级截断）提供了一个清晰的修复范例。增强了 `BaseRenderer` 和 `TokenizeParams` 在处理偏移量方面的协作健壮性。
 - 风险标记：API 元数据修复 , 静默数据损坏修复

# 关联脉络

- PR #54407 [Bugfix][Frontend] Truncate prompt_token_offsets with the prompt: 同一功能线（token 化截断与偏移量对齐）的直接前驱。PR#54407 修复了 token 级别截断时 `prompt_token_offsets` 未同步截断的问题，而本 PR 修复了更早的字符级预截断导致的坐标原点偏移问题。两者共同确保偏移量与 token ID 在各截断步骤后保持一致。
- PR #54539 [Bugfix][Frontend] Truncate the assistant tokens mask with the prompt: 相关修复。该 PR 处理了 `assistant_tokens_mask` 与 token ID 在截断后未对齐的问题，是同一类“并行数组未随截断调整”缺陷的延续。本 PR 处理的是 `prompt_token_offsets` 数组。