// byte-level BPE 编码。
//
// 本包**不认识"词表从哪来"**:所有信息都通过 `VocabView` 传入。
// 这是原创点之一——"一个框架 + 可加载的词表",而不是"一种词表一个构造函数"。
///|
/// `bpe` 与 `vocab` 之间的唯一契约。
///
/// * `tokens` —— index = token id 的字节序列。
/// * `byte_ids` —— 256 项,单字节 token 的 id(不变量 V1 的根基)。
/// * `rank_of` —— token 字节 -> 合并优先级(越小越先合并)。
/// * `id_of` —— token 字节 -> token id。
/// * `specials` —— 特殊 token 字面量 `(字节, id)`。建议按长度降序(性能提示),
/// 但正确性由 `match_special` 自行保证(扫全部取最长)。
pub(all) struct VocabView {
tokens : Array[Bytes]
byte_ids : Array[Int]
rank_of : Map[Bytes, Int]
id_of : Map[Bytes, Int]
/// 短键(≤ 7 字节)的 rank / id 表,键是 `pack_short` 的打包值。
/// 热路径上的相邻对几乎都 ≤ 7 字节,查这两张表就不必为每个候选
/// 拼一个 `Bytes` 出来(每轮合并省下 O(n) 次分配)。
rank_short : Map[Int64, Int]
id_short : Map[Int64, Int]
specials : Array[(Bytes, Int)]
}
///|
/// 打包进 `Int64` 的最长字节数:低 8 位存长度,其余按小端存字节。
pub const SHORT_MAX : Int = 7
///|
/// 把 `piece[start, start+len)` 打包成 `Int64` 键(`len` 必须 ≤ `SHORT_MAX`)。
///
/// 长度放在最低字节、数据从次低字节起,因此不同长度的字节串永不撞键,
/// 也不需要再拼 `Bytes`——这是热路径上省掉的分配。`vocab` 构建短键表时
/// 用的是同一个函数,两端不会漂移。
pub fn pack_short(piece : Bytes, start : Int, len : Int) -> Int64 {
let mut k : Int64 = len.to_int64()
for i = 0; i < len; i = i + 1 {
let byte : Int64 = (piece.unsafe_get(start + i).to_int() & 0xFF).to_int64()
k = k | (byte << ((i + 1) * 8))
}
k
}
///|
/// `[start, end)` 合并后的 rank;不可合并返回 -1。
fn pair_rank(v : VocabView, piece : Bytes, start : Int, end : Int) -> Int {
let len = end - start
if len <= SHORT_MAX {
match v.rank_short.get(pack_short(piece, start, len)) {
Some(r) => r
None => -1
}
} else {
match v.rank_of.get(slice_bytes(piece, start, end)) {
Some(r) => r
None => -1
}
}
}
///|
/// `[start, end)` 对应的 token id。
fn id_at(v : VocabView, piece : Bytes, start : Int, end : Int) -> Int? {
let len = end - start
if len <= SHORT_MAX {
v.id_short.get(pack_short(piece, start, len))
} else {
v.id_of.get(slice_bytes(piece, start, end))
}
}
///|
/// 逐片做 BPE 合并,把结果追加到 `out`。范围是 `piece[from, to)`。
///
/// 段用相对 `offsets` 表示:第 i 段是 `[offsets[i], offsets[i+1])`,合并就是删掉一个边界。
/// `ranks[i]` 缓存段 i 与段 i+1 的可合并优先级,合并后只重算受影响的**两个**位置——
/// 每轮不再为所有相邻对重新拼字节、重新查表(0.7.0 之后的第一轮优化;
/// 输出与优化前逐字节相同,由 corpus 2,935 例与双后端测试保证)。
fn encode_piece(
v : VocabView,
piece : Bytes,
from : Int,
to : Int,
out : Array[Int],
) -> Unit {
let n = to - from
if n == 0 {
return
}
if n == 1 {
// 单个字节没有可合并对象,直接落 id,省掉两个数组。
out.push(v.byte_ids[piece.unsafe_get(from).to_int() & 0xFF])
return
}
let b0 = v.byte_ids[piece.unsafe_get(from).to_int() & 0xFF]
if n == 2 {
// 两字节:只有「合并 / 不合并」两种结局,不需要数组。
if pair_rank(v, piece, from, from + 2) >= 0 {
match id_at(v, piece, from, from + 2) {
Some(id) => out.push(id)
None => out.push(b0)
}
} else {
out.push(b0)
out.push(v.byte_ids[piece.unsafe_get(from + 1).to_int() & 0xFF])
}
return
}
if n == 3 {
// 三字节(一个汉字 / 常见短词):至多两次合并。
// 只要发生过一次合并,剩下两段的拼接就是整串,所以结局只由
// 「有没有可合并的相邻对」和「整串是否有 rank」决定。
let b1 = v.byte_ids[piece.unsafe_get(from + 1).to_int() & 0xFF]
let b2 = v.byte_ids[piece.unsafe_get(from + 2).to_int() & 0xFF]
let r01 = pair_rank(v, piece, from, from + 2)
let r12 = pair_rank(v, piece, from + 1, from + 3)
if r01 < 0 && r12 < 0 {
out.push(b0)
out.push(b1)
out.push(b2)
return
}
if pair_rank(v, piece, from, from + 3) >= 0 {
match id_at(v, piece, from, from + 3) {
Some(id) => {
out.push(id)
return
}
// 词表损坏(rank 有、id 没有)时按「整串不可合并」继续,
// 与通用路径的防御分支保持同样的保守方向。
None => ()
}
}
if r01 >= 0 && (r12 < 0 || r01 <= r12) {
match id_at(v, piece, from, from + 2) {
Some(id) => out.push(id)
None => out.push(b0)
}
out.push(b2)
} else {
out.push(b0)
match id_at(v, piece, from + 1, from + 3) {
Some(id) => out.push(id)
None => out.push(b1)
}
}
return
}
let ids : Array[Int] = []
let offsets : Array[Int] = []
for i = 0; i < n; i = i + 1 {
let b = piece.unsafe_get(from + i).to_int() & 0xFF
// 单字节 token 必须存在,否则 byte-level BPE 不成立(见 V1/V7)。
ids.push(v.byte_ids[b])
offsets.push(i)
}
offsets.push(n)
let ranks : Array[Int] = []
for i = 0; i + 2 <= n; i = i + 1 {
ranks.push(pair_rank(v, piece, from + offsets[i], from + offsets[i + 2]))
}
while offsets.length() > 2 {
let mut best = -1
let mut best_rank = 0
for i = 0; i < ranks.length(); i = i + 1 {
let r = ranks[i]
if r >= 0 && (best < 0 || r < best_rank) {
best = i
best_rank = r
}
}
if best < 0 {
break
}
// 合并后的 token 一定在 id 表里:它能在 rank 表里被找到。
match id_at(v, piece, from + offsets[best], from + offsets[best + 2]) {
Some(id) => ids[best] = id
None => ()
}
let _ = offsets.remove(best + 1)
let _ = ids.remove(best + 1)
let _ = ranks.remove(best)
if best >= 1 {
ranks[best - 1] = pair_rank(
v,
piece,
from + offsets[best - 1],
from + offsets[best + 1],
)
}
if best < ranks.length() {
ranks[best] = pair_rank(v, piece, from + offsets[best], from + offsets[best + 2])
}
}
for id in ids {
out.push(id)
}
}
///|
/// 与 `encode_piece` 同样的合并顺序,但只数 token 数、不产出 id 数组。
fn encode_piece_count(v : VocabView, piece : Bytes, from : Int, to : Int) -> Int {
let n = to - from
if n == 0 {
return 0
}
if n == 1 {
return 1
}
if n == 2 {
return if pair_rank(v, piece, from, from + 2) >= 0 { 1 } else { 2 }
}
if n == 3 {
let r01 = pair_rank(v, piece, from, from + 2)
let r12 = pair_rank(v, piece, from + 1, from + 3)
if r01 < 0 && r12 < 0 {
return 3
}
// 发生过一次合并后只剩两段,其拼接就是整串。
return if pair_rank(v, piece, from, from + 3) >= 0 { 1 } else { 2 }
}
let offsets : Array[Int] = []
for i = 0; i < n; i = i + 1 {
offsets.push(i)
}
offsets.push(n)
let ranks : Array[Int] = []
for i = 0; i + 2 <= n; i = i + 1 {
ranks.push(pair_rank(v, piece, from + offsets[i], from + offsets[i + 2]))
}
while offsets.length() > 2 {
let mut best = -1
let mut best_rank = 0
for i = 0; i < ranks.length(); i = i + 1 {
let r = ranks[i]
if r >= 0 && (best < 0 || r < best_rank) {
best = i
best_rank = r
}
}
if best < 0 {
break
}
let _ = offsets.remove(best + 1)
let _ = ranks.remove(best)
if best >= 1 {
ranks[best - 1] = pair_rank(
v,
piece,
from + offsets[best - 1],
from + offsets[best + 1],
)
}
if best < ranks.length() {
ranks[best] = pair_rank(v, piece, from + offsets[best], from + offsets[best + 2])
}
}
offsets.length() - 1
}
///|
/// 在 `bytes[pos...]` 处找**最长**的特殊 token 字面量。
/// 返回 `(字节长度, token id)`;无匹配返回 `(0, 0)`。
///
/// 刻意"扫全部取最长",而不是依赖 `specials` 已排序:
/// `VocabView` 是公开结构,任何构造者都可能给出未排序的表,
/// 这里必须自带正确性(`Vocab::view` 的降序只是性能提示)。
fn match_special(v : VocabView, bytes : Bytes, pos : Int) -> (Int, Int) {
let n = bytes.length()
let remain = n - pos
let mut best_len = 0
let mut best_id = 0
for entry in v.specials {
let (lit, id) = entry
let l = lit.length()
if l > best_len && l <= remain {
let mut ok = true
for k = 0; k < l; k = k + 1 {
if bytes[pos + k] != lit[k] {
ok = false
break
}
}
if ok {
best_len = l
best_id = id
}
}
}
(best_len, best_id)
}
///|
/// 特殊 token 的**首字节**集合:只有这些字节可能起头,其余位置直接跳过,
/// 不必逐字节调 `match_special`。返回 `None` 表示没有特殊 token,
/// 整段都能走"无特殊 token"的快路径。
fn special_first_bytes(v : VocabView) -> FixedArray[Bool]? {
if v.specials.length() == 0 {
return None
}
let first : FixedArray[Bool] = FixedArray::make(256, false)
for entry in v.specials {
let (lit, _) = entry
if lit.length() > 0 {
first[lit.unsafe_get(0).to_int() & 0xFF] = true
}
}
Some(first)
}
///|
/// 把一段普通文本(不含特殊 token)编码为 token id 序列。
pub fn encode_ordinary(v : VocabView, bytes : Bytes) -> Array[Int] {
let out : Array[Int] = []
encode_ordinary_into(v, bytes, out)
out
}
///|
/// 普通文本的 token 数(不产出 id 数组)。
pub fn count_ordinary(v : VocabView, bytes : Bytes) -> Int {
let starts : Array[Int] = []
let ends : Array[Int] = []
pretokenize_spans(bytes, starts, ends)
let mut total = 0
for i = 0; i < starts.length(); i = i + 1 {
total = total + encode_piece_count(v, bytes, starts[i], ends[i])
}
total
}
///|
/// 完整编码:先把已登记的特殊 token 字面量切出来(最长优先),
/// 其余部分走普通编码。未登记的特殊字面量按普通文本处理(安全默认值)。
pub fn encode(v : VocabView, bytes : Bytes) -> Array[Int] {
let out : Array[Int] = []
let first = special_first_bytes(v)
match first {
None => encode_ordinary_into(v, bytes, out)
Some(first) => {
let n = bytes.length()
let mut i = 0
let mut seg_start = 0
while i < n {
// 首字节不在集合里 -> 这个位置不可能起头,直接跳过。
if !first[bytes.unsafe_get(i).to_int() & 0xFF] {
i = i + 1
continue
}
let (sp_len, sp_id) = match_special(v, bytes, i)
if sp_len > 0 {
if i > seg_start {
encode_ordinary_into(v, slice_bytes(bytes, seg_start, i), out)
}
out.push(sp_id)
i = i + sp_len
seg_start = i
} else {
i = i + 1
}
}
if n > seg_start {
encode_ordinary_into(v, slice_bytes(bytes, seg_start, n), out)
}
}
}
out
}
///|
/// 完整编码的"只计数"版本:与 `encode` 同一条切分路径,但不分配 id 数组。
/// `encode_count(v, b) == encode(v, b).length()` 由单元测试守着。
pub fn encode_count(v : VocabView, bytes : Bytes) -> Int {
let first = special_first_bytes(v)
match first {
None => count_ordinary(v, bytes)
Some(first) => {
let n = bytes.length()
let mut total = 0
let mut i = 0
let mut seg_start = 0
while i < n {
if !first[bytes.unsafe_get(i).to_int() & 0xFF] {
i = i + 1
continue
}
let (sp_len, _) = match_special(v, bytes, i)
if sp_len > 0 {
if i > seg_start {
total = total + count_ordinary(v, slice_bytes(bytes, seg_start, i))
}
total = total + 1
i = i + sp_len
seg_start = i
} else {
i = i + 1
}
}
if n > seg_start {
total = total + count_ordinary(v, slice_bytes(bytes, seg_start, n))
}
total
}
}
}
///|
/// `encode_ordinary` 的"追加到已有数组"版本,避免中间拷贝。
fn encode_ordinary_into(v : VocabView, bytes : Bytes, out : Array[Int]) -> Unit {
let starts : Array[Int] = []
let ends : Array[Int] = []
pretokenize_spans(bytes, starts, ends)
for i = 0; i < starts.length(); i = i + 1 {
encode_piece(v, bytes, starts[i], ends[i], out)
}
}