use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use crate::compressor::core::codecs::FnEncoder;
use super::util::{push_le, read_le};
use crate::compressor::core::data::{Stream, Type};
use crate::compressor::core::error::NodeError;
use crate::compressor::core::exec::NodeCx;
pub(crate) const SORT_PID: i32 = crate::graph::params::tokenize::SORT;
const TOKENIZER_PID: i32 = 1;
pub(crate) static TOKENIZE: FnEncoder = FnEncoder(tokenize);
pub(crate) static TOKENIZE_STRING: FnEncoder = FnEncoder(tokenize_string);
fn should_sort(cx: &NodeCx<'_, '_>) -> bool {
cx.int(SORT_PID).is_some_and(|value| value != 0)
}
fn index_width(size: usize, elt_width: usize) -> Result<usize, NodeError> {
if size as u64 > u64::from(u32::MAX) {
return Err(NodeError::InvalidInput(
"only 4 byte indices are supported",
));
}
if size <= 1 << 8 {
Ok(1)
} else if elt_width > 1 && size <= 1 << 16 {
Ok(2)
} else if elt_width > 2 {
Ok(4)
} else {
Err(NodeError::InvalidInput("impossible alphabet size"))
}
}
fn tokenize<'a>(cx: &mut NodeCx<'_, 'a>, inputs: &[Stream<'a>]) -> Result<(), NodeError> {
let input = &inputs[0];
if cx.copy_param(TOKENIZER_PID).is_some() {
return Err(NodeError::UnsupportedParam("custom tokenizers"));
}
let sort = should_sort(cx);
if input.ty() != Type::Numeric && sort {
return Err(NodeError::InvalidInput(
"sort only works on numeric inputs",
));
}
let width = input.width();
if !matches!(width, 1 | 2 | 4 | 8) {
return Err(NodeError::InvalidInput("element width not supported"));
}
let mut map = TokenMap::new();
let mut alphabet: Vec<u64> = Vec::new();
let mut ids: Vec<usize> = Vec::with_capacity(input.n_elts());
for element in input.bytes().chunks_exact(width) {
let token = read_le(element);
ids.push(map.position(token, alphabet.len()));
if map.len() > alphabet.len() {
alphabet.push(token);
}
}
let ranks: Option<Vec<usize>> = sort.then(|| {
let mut order: Vec<usize> = (0..alphabet.len()).collect();
order.sort_unstable_by_key(|&id| alphabet[id]);
let mut ranks = alloc::vec![0usize; alphabet.len()];
for (rank, &id) in order.iter().enumerate() {
ranks[id] = rank;
}
alphabet.sort_unstable();
ranks
});
let idx_width = index_width(alphabet.len(), width)?;
let mut alphabet_bytes = Vec::with_capacity(alphabet.len() * width);
for &token in &alphabet {
push_le(&mut alphabet_bytes, token, width);
}
cx.emit(0, width, alphabet_bytes)?;
if let Some(ranks) = &ranks {
ids.iter_mut().for_each(|id| *id = ranks[*id]);
}
let mut index_bytes = Vec::with_capacity(ids.len() * idx_width);
match idx_width {
1 => index_bytes.extend(ids.iter().map(|&id| id as u8)),
2 => ids
.iter()
.for_each(|&id| index_bytes.extend_from_slice(&(id as u16).to_le_bytes())),
_ => ids
.iter()
.for_each(|&id| index_bytes.extend_from_slice(&(id as u32).to_le_bytes())),
}
cx.emit(1, idx_width, index_bytes)?;
Ok(())
}
struct TokenMap {
keys: Vec<u64>,
positions: Vec<usize>,
len: usize,
shift: u32,
}
impl TokenMap {
const EMPTY: usize = usize::MAX;
fn new() -> Self {
Self::with_capacity_bits(10)
}
fn with_capacity_bits(bits: u32) -> Self {
Self {
keys: alloc::vec![0; 1 << bits],
positions: alloc::vec![Self::EMPTY; 1 << bits],
len: 0,
shift: 64 - bits,
}
}
const fn len(&self) -> usize {
self.len
}
fn slot(&self, key: u64) -> usize {
let mask = self.keys.len() - 1;
let mut slot = (key.wrapping_mul(0x9E37_79B9_7F4A_7C15) >> self.shift) as usize;
while self.positions[slot] != Self::EMPTY && self.keys[slot] != key {
slot = (slot + 1) & mask;
}
slot
}
fn position(&mut self, key: u64, next: usize) -> usize {
let mut slot = self.slot(key);
if self.positions[slot] != Self::EMPTY {
return self.positions[slot];
}
if (self.len + 1) * 2 > self.keys.len() {
let mut grown = Self::with_capacity_bits(65 - self.shift);
for (&key, &position) in self.keys.iter().zip(&self.positions) {
if position != Self::EMPTY {
let slot = grown.slot(key);
grown.keys[slot] = key;
grown.positions[slot] = position;
}
}
grown.len = self.len;
*self = grown;
slot = self.slot(key);
}
self.keys[slot] = key;
self.positions[slot] = next;
self.len += 1;
next
}
}
fn tokenize_string<'a>(cx: &mut NodeCx<'_, 'a>, inputs: &[Stream<'a>]) -> Result<(), NodeError> {
let input = &inputs[0];
let sort = should_sort(cx);
let lens = input.lens();
let mut keys: Vec<&[u8]> = Vec::with_capacity(lens.len());
let mut rest = input.bytes();
for &len in lens {
let (key, tail) = rest.split_at(len as usize);
keys.push(key);
rest = tail;
}
let mut positions: BTreeMap<&[u8], usize> = BTreeMap::new();
let mut alphabet: Vec<&[u8]> = Vec::new();
for &key in &keys {
positions.entry(key).or_insert_with(|| {
alphabet.push(key);
alphabet.len() - 1
});
}
if sort {
alphabet.sort_unstable();
for (rank, key) in alphabet.iter().enumerate() {
positions.insert(key, rank);
}
}
let idx_width = if alphabet.len() as u64 > u64::from(u32::MAX) {
return Err(NodeError::InvalidInput(
"only 4 byte indices are supported",
));
} else if alphabet.len() <= 1 << 8 {
1
} else if alphabet.len() <= 1 << 16 {
2
} else {
4
};
let mut content = Vec::new();
let mut alphabet_lens = Vec::with_capacity(alphabet.len());
for key in &alphabet {
content.extend_from_slice(key);
alphabet_lens.push(key.len() as u32);
}
cx.emit_strings(0, content, alphabet_lens)?;
let mut index_bytes = Vec::with_capacity(keys.len() * idx_width);
for key in &keys {
push_le(&mut index_bytes, positions[key] as u64, idx_width);
}
cx.emit(1, idx_width, index_bytes)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::TokenMap;
#[test]
fn token_map_keeps_first_positions_across_growth() {
let mut map = TokenMap::new();
for key in 0..5000u64 {
let key = key * 1024;
assert_eq!(map.position(key, map.len()), (key / 1024) as usize);
}
assert_eq!(map.len(), 5000);
for key in (0..5000u64).rev() {
assert_eq!(map.position(key * 1024, usize::MAX - 1), key as usize);
}
assert_eq!(map.len(), 5000);
}
}