use core::{cell::RefCell, ops::Range};
use windit::{
plan::Span,
split::{Chunk, MeasureText},
windowed::Windowed,
};
use super::{
alloc::{self, reserve, reserve_str},
config::MAX_TOKENS,
embedding::Embedding,
error::{AllocationSite, Error, PromptFillsWindow, Result},
prompt::Prompt,
text::Framing,
tokenizer::{
SplitSpecialTokens,
bpe::{Encoded, SPACE_MARK, TokenId, TokenizerScratch},
image::TokenizerImage,
},
};
use crate::embeddings::{
aggregate::AggregatePolicyKind,
long_text::{self, LongTextOptions, RangeMeasure},
};
#[inline]
fn marked(ch: char) -> char {
if ch == ' ' { SPACE_MARK } else { ch }
}
#[derive(Debug)]
struct Seams {
bridges: Vec<(Vec<char>, Vec<char>)>,
reach: usize,
cuts: bool,
}
impl Seams {
fn of(image: &TokenizerImage) -> Result<Self> {
let text = |id| {
image
.token(id)
.and_then(|bytes| core::str::from_utf8(bytes).ok())
};
let marked_chars = |token: &str| -> Result<Vec<char>> {
let mut chars = alloc::with_capacity(token.chars().count(), AllocationSite::PieceTable)?;
chars.extend(token.chars().map(marked));
Ok(chars)
};
let mut cuts = image
.added()
.all(|id| text(id).is_some_and(|literal| !literal.chars().any(|c| marked(c) == SPACE_MARK)));
let mut bridges = Vec::new();
for (left, right) in image.bridges() {
match (text(left), text(right)) {
(Some(left), Some(right)) => {
let left = marked_chars(left)?;
let right = marked_chars(right)?;
cuts &= !inner_boundary(&left) && !inner_boundary(&right);
reserve(&mut bridges, 1, AllocationSite::PieceTable)?;
bridges.push((left, right));
}
_ => cuts = false,
}
}
let reach = bridges
.iter()
.map(|(left, _)| left.len())
.max()
.unwrap_or(0);
Ok(Self {
bridges,
reach,
cuts,
})
}
fn left_meets(&self, text: &str, at: usize) -> bool {
self
.bridges
.iter()
.any(|(left, _)| ends_with(&text[..at], left))
}
fn bridged(&self, text: &str, at: usize) -> bool {
self.bridges.iter().any(|(left, right)| {
ends_with(&text[..at], left)
&& text[at..]
.chars()
.map(marked)
.take(right.len())
.eq(right.iter().copied())
})
}
}
fn ends_with(text: &str, token: &[char]) -> bool {
text
.chars()
.rev()
.map(marked)
.take(token.len())
.eq(token.iter().rev().copied())
}
fn inner_boundary(token: &[char]) -> bool {
token
.windows(2)
.any(|pair| pair[1] == SPACE_MARK && pair[0] != SPACE_MARK)
}
fn word_boundary(previous: char, ch: char) -> bool {
marked(ch) == SPACE_MARK && marked(previous) != SPACE_MARK
}
fn join_point(prompt: &str, seams: &Seams) -> usize {
if !seams.cuts {
return 0;
}
let mut join = 0;
let mut previous = None;
for (at, ch) in prompt.char_indices() {
if let Some(previous) = previous
&& word_boundary(previous, ch)
&& !seams.left_meets(prompt, at)
{
join = at;
}
previous = Some(ch);
}
join
}
#[derive(Debug)]
struct PromptJoin {
text: String,
join: usize,
head: usize,
}
impl PromptJoin {
fn new(
image: &TokenizerImage,
prompt: &Prompt<'_>,
split: SplitSpecialTokens,
seams: &Seams,
scratch: &mut TokenizerScratch,
) -> Result<Self> {
let pieces = prompt.pieces();
let mut text = String::new();
reserve_str(
&mut text,
pieces.iter().map(|piece| piece.len()).sum(),
AllocationSite::PieceTable,
)?;
for piece in pieces {
text.push_str(piece);
}
let join = join_point(&text, seams);
let mut head = 0;
if join > 0 {
let (mut ids, mut spans) = (Vec::new(), Vec::new());
image.encode_joint(
&Prompt::Bare,
&text,
split,
scratch,
Encoded {
ids: &mut ids,
spans: Some(&mut spans),
},
)?;
head = spans[1..spans.len() - 1]
.iter()
.filter(|span| span.start < join)
.count();
}
Ok(Self { text, join, head })
}
#[inline]
fn tail(&self) -> &str {
&self.text[self.join..]
}
}
#[derive(Debug, Default)]
struct PieceTable {
cuts: Vec<usize>,
before: Vec<usize>,
}
impl PieceTable {
fn build(
image: &TokenizerImage,
text: &str,
split: SplitSpecialTokens,
seams: &Seams,
scratch: &mut TokenizerScratch,
) -> Result<Self> {
let mut table = Self::default();
if !seams.cuts {
return Ok(table);
}
let (mut ids, mut spans) = (Vec::new(), Vec::new());
image.encode_joint(
&Prompt::Bare,
text,
split,
scratch,
Encoded {
ids: &mut ids,
spans: Some(&mut spans),
},
)?;
let content = &spans[1..spans.len() - 1];
let mut token = 0;
let mut previous = None;
for (at, ch) in text.char_indices() {
if let Some(previous) = previous
&& word_boundary(previous, ch)
&& !seams.bridged(text, at)
{
while token < content.len() && content[token].start < at {
token += 1;
}
reserve(&mut table.cuts, 1, AllocationSite::PieceTable)?;
reserve(&mut table.before, 1, AllocationSite::PieceTable)?;
table.cuts.push(at);
table.before.push(token);
}
previous = Some(ch);
}
Ok(table)
}
}
#[derive(Debug, Default)]
struct Work {
scratch: TokenizerScratch,
ids: Vec<TokenId>,
}
#[derive(Debug)]
pub(crate) struct JointMeasure<'a> {
image: &'a TokenizerImage,
text: &'a str,
split: SplitSpecialTokens,
seams: Seams,
prompt: PromptJoin,
table: PieceTable,
longest: usize,
work: RefCell<Work>,
failure: RefCell<Option<Error>>,
}
impl<'a> JointMeasure<'a> {
pub(crate) fn new(
image: &'a TokenizerImage,
text: &'a str,
prompt: Prompt<'_>,
split: SplitSpecialTokens,
) -> Result<Self> {
let seams = Seams::of(image)?;
let mut work = Work::default();
let prompt = PromptJoin::new(image, &prompt, split, &seams, &mut work.scratch)?;
let table = PieceTable::build(image, text, split, &seams, &mut work.scratch)?;
Ok(Self {
image,
text,
split,
seams,
prompt,
table,
longest: image.longest_token().max(1),
work: RefCell::new(work),
failure: RefCell::new(None),
})
}
fn within(&self, start: usize, end: usize, limit: usize) -> Result<Option<usize>> {
let head = self.prompt.head;
let tail = self.prompt.tail();
let floor = |bytes: usize| bytes.div_ceil(self.longest);
let last = self.table.cuts.partition_point(|&cut| cut <= end);
let total = match self.first_cut(start) {
Some(first) if first < last => {
let (c1, c2) = (self.table.cuts[first], self.table.cuts[last - 1]);
let middle = self.table.before[last - 1] - self.table.before[first];
let right_floor = floor(end - c2);
let left_floor = 2 + floor(tail.len() + (c1 - start));
if head
.saturating_add(left_floor)
.saturating_add(middle)
.saturating_add(right_floor)
> limit
{
return Ok(None);
}
let left = self.count(Prompt::Custom(tail), &self.text[start..c1])?;
if head
.saturating_add(left)
.saturating_add(middle)
.saturating_add(right_floor)
> limit
{
return Ok(None);
}
let right = self.count(Prompt::Bare, &self.text[c2..end])? - 2;
head + left + middle + right
}
_ => {
if head.saturating_add(2 + floor(tail.len() + (end - start))) > limit {
return Ok(None);
}
head + self.count(Prompt::Custom(tail), &self.text[start..end])?
}
};
Ok((total <= limit).then_some(total))
}
fn first_cut(&self, start: usize) -> Option<usize> {
let cuts = &self.table.cuts;
let reach = self.seams.reach;
(cuts.partition_point(|&cut| cut <= start)..cuts.len()).find(|&index| {
reach == 0
|| self.text[..cuts[index]]
.char_indices()
.rev()
.nth(reach - 1)
.is_some_and(|(at, _)| at >= start)
})
}
fn count(&self, prompt: Prompt<'_>, text: &str) -> Result<usize> {
let mut work = self.work.borrow_mut();
let Work { scratch, ids } = &mut *work;
ids.clear();
self.image.encode_joint(
&prompt,
text,
self.split,
scratch,
Encoded { ids, spans: None },
)?;
Ok(ids.len())
}
fn range_of(&self, s: &str) -> Option<(usize, usize)> {
let start = (s.as_ptr() as usize).checked_sub(self.text.as_ptr() as usize)?;
let end = start.checked_add(s.len())?;
self.text.get(start..end).map(|_| (start, end))
}
}
impl MeasureText for JointMeasure<'_> {
fn measure(&self, s: &str) -> usize {
self.measure_within(s, usize::MAX).unwrap_or(usize::MAX)
}
fn measure_within(&self, s: &str, limit: usize) -> Option<usize> {
let measured = match self.range_of(s) {
Some((start, end)) => self.within(start, end, limit),
None => self
.count(Prompt::Custom(&self.prompt.text), s)
.map(|ids| (ids <= limit).then_some(ids)),
};
measured.unwrap_or_else(|failure| {
self.failure.borrow_mut().get_or_insert(failure);
None
})
}
}
impl RangeMeasure for JointMeasure<'_> {
type Error = Error;
fn measure_range(&self, start: usize, end: usize) -> Result<usize> {
Ok(self.within(start, end, usize::MAX)?.unwrap_or(usize::MAX))
}
fn take_failure(&self) -> Option<Error> {
self.failure.borrow_mut().take()
}
fn reserve_chunks(&self, chunks: &mut Vec<Chunk>, additional: usize) -> Result<()> {
reserve(chunks, additional, AllocationSite::WindowList)
}
}
#[derive(Debug)]
pub(crate) struct Planned {
pub(crate) range: Range<usize>,
pub(crate) ids: Vec<TokenId>,
}
pub(crate) fn plan(
image: &TokenizerImage,
text: &str,
prompt: Prompt<'_>,
split: SplitSpecialTokens,
geometry: &LongTextOptions,
framing: Framing,
scratch: &mut TokenizerScratch,
) -> Result<Vec<Planned>> {
long_text::validate_long_input(text, geometry, MAX_TOKENS)?;
let window = geometry.window_options();
window.validate()?;
if window.window() <= framing.ids() {
return Err(Error::PromptFillsWindow(PromptFillsWindow::new(
window.window(),
framing.ids(),
)));
}
if text.is_empty() {
return Err(Error::EmptyText);
}
let measure = JointMeasure::new(image, text, prompt, split)?;
let chunks: Vec<Chunk> = long_text::chunk_long(&measure, text, &window, MAX_TOKENS)?;
let mut windows = alloc::with_capacity(chunks.len(), AllocationSite::WindowList)?;
for chunk in chunks {
let chunk_text = chunk
.as_str(text)
.expect("the chunker cuts on char boundaries");
let mut ids = Vec::new();
image.encode_joint(
&prompt,
chunk_text,
split,
scratch,
Encoded {
ids: &mut ids,
spans: None,
},
)?;
if ids.len() > MAX_TOKENS {
return Err(Error::PromptFillsWindow(PromptFillsWindow::new(
MAX_TOKENS,
framing.ids(),
)));
}
windows.push(Planned {
range: chunk.start()..chunk.end(),
ids,
});
}
Ok(windows)
}
#[derive(Debug, Clone)]
pub struct WindowEmbedding {
ordinal: usize,
byte_range: Range<usize>,
token_span: Span,
embedding: Embedding,
}
impl WindowEmbedding {
pub(crate) const fn new(
ordinal: usize,
byte_range: Range<usize>,
token_span: Span,
embedding: Embedding,
) -> Self {
Self {
ordinal,
byte_range,
token_span,
embedding,
}
}
#[inline]
pub const fn ordinal(&self) -> usize {
self.ordinal
}
#[inline]
pub const fn byte_start(&self) -> usize {
self.byte_range.start
}
#[inline]
pub const fn byte_end(&self) -> usize {
self.byte_range.end
}
#[inline]
pub fn byte_range(&self) -> Range<usize> {
self.byte_range.clone()
}
#[inline]
pub const fn token_span(&self) -> Span {
self.token_span
}
#[inline]
pub const fn token_count(&self) -> usize {
self.token_span.len()
}
#[inline]
pub const fn embedding(&self) -> &Embedding {
&self.embedding
}
#[must_use]
#[inline]
pub fn into_embedding(self) -> Embedding {
self.embedding
}
}
pub(crate) fn fold_windows(
mut windows: Vec<WindowEmbedding>,
fold: &AggregatePolicyKind,
) -> Result<Embedding> {
if windows.len() == 1 {
return Ok(windows.pop().expect("one window").into_embedding());
}
let windowed: Vec<Windowed<Embedding>> = alloc::collect(
windows
.into_iter()
.map(|window| Windowed::new(window.embedding, window.token_span)),
AllocationSite::WindowList,
)?;
Ok(windit::aggregate::aggregate(
fold.into_policy().as_ref(),
&windowed,
)?)
}
#[cfg(test)]
mod tests;