
看到一段循环每秒能扫描多少字节,我会先确认它输出的是什么。如果输出只有空格位置,就算换成SIMD,也还没有实现模型需要的token IDs。
Tokenizers组件文档把归一化、预分词、模型分词与后处理分开说明。memchr文档则介绍字节搜索。两者在文本流水线里都可能有用,但承担的契约不同。这些是持续维护的资料,本轮2026-10-23访问,不能当作十月新品新闻。

从一个中文字符串开始
UTF-8的字符可能占多个字节。ASCII空格字节0x20不会出现在其他UTF-8字符的续字节中,因此按字节找它可以得到有效字符边界;但这并不意味着按任意字节位置切片都安全。
下面只寻找ASCII空格。它故意不处理制表符、换行、不间断空格等其他分隔符,也不声称与模型预分词规则一致。
fn spaces(bytes: &[u8]) -> Vec<usize> {
bytes.iter().enumerate().filter_map(|(i,&b)| (b == b' ').then_some(i)).collect()
}
fn main() {
let text = "Rust 学习🙂 a";
let positions = spaces(text.as_bytes());
assert_eq!(positions, vec![4,15]);
assert!(positions.iter().all(|&i| text.is_char_boundary(i)));
assert_eq!(text.split_whitespace().collect::<Vec<_>>(),vec!["Rust","学习🙂","a"]);
assert!(spaces("学习🙂".as_bytes()).is_empty());
assert_eq!(spaces(&[b' ';33]).len(),33);
println!("byte-space boundaries checked; no BPE token IDs produced");
}
本地rustc 1.98.0验证了中文、emoji、空输入语义及33字节连续空格。程序没有输出词表ID。Rust字符串的索引按字节计,这里的15是编码后的字节位置,不是第15个汉字。
进入 BPE 还要什么
BPE通常还需要模型的初始表示、词表、merge规则、特殊token处理及可能的byte-level映射。是否先做Unicode归一化、如何处理前导空格,都可能影响最终ID。不能把split_whitespace的结果随便分配数字后称作兼容分词器。
要证明兼容,我会固定tokenizer文件及哈希,比较一组输入的完整ID序列和解码结果:中英文混排、组合字符、emoji、连续空白、特殊token文本和空字符串都应包含。只有字符数或token数量相同仍不够,因为ID本身可能不同。
优化要守住输出合同
标量和SIMD路径先比较完全一致的位置数组,覆盖向量宽度前后、空输入与尾块,再谈速度。以中文为主的语料空格少,优化这一步对整条流水线的收益可能很小;这属于待测假设,不能凭微内核吞吐推算端到端提升。
本轮只验证标量字节扫描,没有实现BPE、运行SIMD或加载模型分词器。这样保留“还没做到”的部分,反而能避免把一个正确的小工具误介绍成另一个更大的工具。
本文指出仅优化空格字节扫描(如用SIMD加速UTF-8中ASCII空格查找)并不等价于实现完整BPE分词器。BPE需严格满足模型所需的token ID输出合同,涵盖Unicode归一化、词表映射、merge规则、特殊token处理及byte-level一致性验证。强调标量与SIMD路径必须先保证输出完全一致,再谈性能提升,并警示微内核优化不能替代端到端分词兼容性验证。

467

被折叠的 条评论
为什么被折叠?



