Prhub

#46113 [Bugfix] [Rust Frontend] Fix stop string truncation with repeated matches

原始 PR 作者 reidliu41 合并时间 2026-06-22 14:11 文件变更 1 提交数 2 评论 2 代码增减 +8 / -1

执行摘要

修复 Rust 前端 stop string 截断 bug

PR body 指出:当同一 stop string 在新解码文本中多次出现时,Rust 实现使用 rposition() 选择搜索窗口内的最右侧匹配,而 Python V1 detokenization 使用 find() 选择最左侧匹配,导致行为不一致。例如,`output="Answer

", stop="\n"时,Rust 可能截断到"Answer\n"而不是正确的"Answer",在include_stop_str_in_output=false` 时仍保留了 stop 字符串。

该 PR 是一个干净、聚焦的 bugfix,值得合并。对于关注 Rust 前端或 tokenization 逻辑的工程师,可以了解 positionrposition 的差异及其对 stop string 匹配的影响。变更简单,易于理解和审核。

讨论亮点

Review 讨论非常简洁。njhill 和 BugenZhao 均批准了该 PR。BugenZhao 还触发了 Codex review,Codex 未发现重大问题。未发现设计争议或未解决疑虑。

实现拆解

  1. 修改 matches_stop_string 函数的核心搜索方向:在 rust/src/text/src/output/decoded.rs 中将 .rposition(|w| w == ss) 替换为 .position(|w| w == ss)。这个函数负责在给定的输出字节切片中查找 stop 字符串的起始位置;rposition 从右向左搜索,position 从左向右搜索。
  2. 新增测试用例:在同一个文件的测试模块中新增 stop_string_matches_leftmost_with_multiple_new_bytes 测试,验证当输出包含重复 stop 字符串时(如 `"Answer

"stop="\n"new_bytes=2),函数返回最左侧匹配(Some((0, 6))`),与 Python 行为一致。

  1. 仅修改了一个文件,一行核心逻辑变更,改动极小且聚焦。
文件 模块 状态 重要度
rust/src/text/src/output/decoded.rs 文本处理 modified 5.54

关键符号

matches_stop_string

关键源码片段

rust/src/text/src/output/decoded.rs core-logic

唯一修改的文件,包含核心逻辑变更和新增测试。修改了 `matches_stop_string` 函数中的搜索方向,并添加了测试用例 `stop_string_matches_leftmost_with_multiple_new_bytes`。

// rust/src/text/src/output/decoded.rs/// 如果匹配到 stop 字符串,返回 (stop 字符串索引, stop 字符串在 output 中的起始字节位置)
/// 变更:使用 position() 而非 rposition() 来查找最左侧匹配,与 Python V1 detokenizer 的 find() 行为一致
fn matches_stop_string(stops: &[String], output: &str, new_bytes: usize) -> Option<(usize, usize)> {
    // 按字节操作以避免 UTF-8 边界问题
    let output = output.as_bytes();
    // 计算搜索窗口的起始偏移:output 总字节数 + 1 - new_bytes
    let next_off = (output.len() + 1) - new_bytes;
    stops.iter()
        .map(|ss| (ss.as_bytes(), ss.len(), next_off.saturating_sub(ss.len())))
        .enumerate()
        .find_map(|(ss_idx, (ss, len, start_off))| {
            // 在 [start_off..] 的子切片上搜索 stop 字符串
            output[start_off..]
                .windows(len)
                .position(|w| w == ss) // 原来为 .rposition(...),从右向左搜索
                .map(|pos| (ss_idx, start_off + pos))
        })
}#[cfg(test)]
mod tests {
    /* ... */
    #[test]
    fn stop_string_matches_leftmost_with_multiple_new_bytes() {
        // 测试:同一 stop 字符串在新解码文本中出现多次时,应匹配最左侧
        let stops = vec!["\n".to_string()];
        let result = matches_stop_string(&stops, "Answer\n\n", 2);
        // 期望匹配到索引 6(第一个换行符位置),而非索引 7(第二个换行符)
        assert_eq!(result, Some((0, 6)));
    }
    /* ... */
}

评论区精华

Codex review other

BugenZhao 请求 Codex 审查,Codex 回复未发现重大问题。

结论:无问题。 · 已解决

风险与影响

风险极低。变更仅将 rposition() 替换为 position(),是 Rust 标准库提供的方法,语义明确。新增的测试用例覆盖了关键的回归场景。但需注意该行为变更可能与部分依赖右侧匹配的隐式假设冲突(理论上不存在,因 Python 行为亦然)。建议确认 Python V1 detokenization 的 find() 行为一直如此,确保一致性。

影响范围:影响 Rust 前端中所有使用 matches_stop_string 的场景,即当 stop 字符串在新解码文本中出现多次时的截断行为。影响程度:小范围 bugfix,修复了边缘 case。用户可见性:用户在使用 Rust 前端且 stop 字符串在单次解码增量中出现多次时,会观察到更正确的截断结果。

极少回归风险

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论