#[derive(Debug, Clone)]
pub struct PrefillChunk<'a> {
tokens: &'a [u32],
index_pos: usize,
is_last: bool,
chunk_idx: usize,
total_chunks: usize,
}
impl<'a> PrefillChunk<'a> {
pub fn tokens(&self) -> &[u32] {
self.tokens
}
pub fn index_pos(&self) -> usize {
self.index_pos
}
pub fn is_last(&self) -> bool {
self.is_last
}
pub fn chunk_idx(&self) -> usize {
self.chunk_idx
}
pub fn total_chunks(&self) -> usize {
self.total_chunks
}
pub fn len(&self) -> usize {
self.tokens.len()
}
pub fn is_empty(&self) -> bool {
self.tokens.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct ChunkedPrefill<'a> {
tokens: &'a [u32],
chunk_size: usize,
current_pos: usize,
total_chunks: usize,
current_chunk: usize,
}
impl<'a> ChunkedPrefill<'a> {
pub fn new(tokens: &'a [u32], chunk_size: usize) -> Self {
assert!(chunk_size > 0, "chunk_size must be > 0");
let total_chunks = if tokens.is_empty() {
0
} else {
tokens.len().div_ceil(chunk_size)
};
Self {
tokens,
chunk_size,
current_pos: 0,
total_chunks,
current_chunk: 0,
}
}
pub fn next_chunk(&mut self) -> Option<PrefillChunk<'a>> {
if self.current_pos >= self.tokens.len() {
return None;
}
let start = self.current_pos;
let end = (start + self.chunk_size).min(self.tokens.len());
let chunk_tokens = &self.tokens[start..end];
let chunk_idx = self.current_chunk;
self.current_pos = end;
self.current_chunk += 1;
Some(PrefillChunk {
tokens: chunk_tokens,
index_pos: start,
is_last: end >= self.tokens.len(),
chunk_idx,
total_chunks: self.total_chunks,
})
}
pub fn is_done(&self) -> bool {
self.current_pos >= self.tokens.len()
}
pub fn num_chunks(&self) -> usize {
self.total_chunks
}
pub fn remaining_chunks(&self) -> usize {
self.total_chunks - self.current_chunk
}
pub fn total_tokens(&self) -> usize {
self.tokens.len()
}
pub fn tokens_processed(&self) -> usize {
self.current_pos
}
pub fn chunk_size(&self) -> usize {
self.chunk_size
}
pub fn reset(&mut self) {
self.current_pos = 0;
self.current_chunk = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_chunking() {
let tokens: Vec<u32> = (0..10).collect();
let mut prefill = ChunkedPrefill::new(&tokens, 4);
assert_eq!(prefill.num_chunks(), 3); assert!(!prefill.is_done());
let c = prefill.next_chunk().unwrap();
assert_eq!(c.tokens(), &[0, 1, 2, 3]);
assert_eq!(c.index_pos(), 0);
assert!(!c.is_last());
assert_eq!(c.chunk_idx(), 0);
assert_eq!(c.len(), 4);
let c = prefill.next_chunk().unwrap();
assert_eq!(c.tokens(), &[4, 5, 6, 7]);
assert_eq!(c.index_pos(), 4);
assert!(!c.is_last());
let c = prefill.next_chunk().unwrap();
assert_eq!(c.tokens(), &[8, 9]);
assert_eq!(c.index_pos(), 8);
assert!(c.is_last());
assert_eq!(c.chunk_idx(), 2);
assert!(prefill.is_done());
assert!(prefill.next_chunk().is_none());
}
#[test]
fn exact_division() {
let tokens: Vec<u32> = (0..8).collect();
let mut prefill = ChunkedPrefill::new(&tokens, 4);
assert_eq!(prefill.num_chunks(), 2);
let c0 = prefill.next_chunk().unwrap();
assert_eq!(c0.tokens(), &[0, 1, 2, 3]);
assert!(!c0.is_last());
let c1 = prefill.next_chunk().unwrap();
assert_eq!(c1.tokens(), &[4, 5, 6, 7]);
assert!(c1.is_last());
assert!(prefill.is_done());
}
#[test]
fn single_chunk() {
let tokens: Vec<u32> = (0..3).collect();
let mut prefill = ChunkedPrefill::new(&tokens, 10);
assert_eq!(prefill.num_chunks(), 1);
let c = prefill.next_chunk().unwrap();
assert_eq!(c.tokens(), &[0, 1, 2]);
assert_eq!(c.index_pos(), 0);
assert!(c.is_last());
assert_eq!(c.chunk_idx(), 0);
assert!(prefill.is_done());
}
#[test]
fn chunk_size_equals_length() {
let tokens: Vec<u32> = (0..5).collect();
let mut prefill = ChunkedPrefill::new(&tokens, 5);
assert_eq!(prefill.num_chunks(), 1);
let c = prefill.next_chunk().unwrap();
assert_eq!(c.tokens().len(), 5);
assert!(c.is_last());
}
#[test]
fn chunk_size_one() {
let tokens: Vec<u32> = (0..3).collect();
let mut prefill = ChunkedPrefill::new(&tokens, 1);
assert_eq!(prefill.num_chunks(), 3);
for i in 0..3 {
let c = prefill.next_chunk().unwrap();
assert_eq!(c.tokens(), &[i]);
assert_eq!(c.index_pos(), i as usize);
assert_eq!(c.is_last(), i == 2);
}
}
#[test]
fn empty_tokens() {
let tokens: Vec<u32> = vec![];
let mut prefill = ChunkedPrefill::new(&tokens, 4);
assert_eq!(prefill.num_chunks(), 0);
assert!(prefill.is_done());
assert!(prefill.next_chunk().is_none());
}
#[test]
fn reset_works() {
let tokens: Vec<u32> = (0..6).collect();
let mut prefill = ChunkedPrefill::new(&tokens, 4);
while prefill.next_chunk().is_some() {}
assert!(prefill.is_done());
prefill.reset();
assert!(!prefill.is_done());
assert_eq!(prefill.remaining_chunks(), 2);
let c = prefill.next_chunk().unwrap();
assert_eq!(c.tokens(), &[0, 1, 2, 3]);
}
#[test]
fn remaining_chunks_decrements() {
let tokens: Vec<u32> = (0..10).collect();
let mut prefill = ChunkedPrefill::new(&tokens, 3);
assert_eq!(prefill.num_chunks(), 4); assert_eq!(prefill.remaining_chunks(), 4);
prefill.next_chunk();
assert_eq!(prefill.remaining_chunks(), 3);
assert_eq!(prefill.tokens_processed(), 3);
prefill.next_chunk();
assert_eq!(prefill.remaining_chunks(), 2);
assert_eq!(prefill.tokens_processed(), 6);
}
#[test]
fn total_chunks_info() {
let tokens: Vec<u32> = (0..10).collect();
let mut prefill = ChunkedPrefill::new(&tokens, 4);
let c = prefill.next_chunk().unwrap();
assert_eq!(c.total_chunks(), 3);
}
#[test]
#[should_panic(expected = "chunk_size must be > 0")]
fn zero_chunk_size_panics() {
let tokens: Vec<u32> = vec![1, 2, 3];
ChunkedPrefill::new(&tokens, 0);
}
#[test]
fn large_prompt() {
let tokens: Vec<u32> = (0..10_000).collect();
let mut prefill = ChunkedPrefill::new(&tokens, 512);
assert_eq!(prefill.num_chunks(), 20);
let mut chunk_count = 0;
let mut last_was_last = false;
while let Some(c) = prefill.next_chunk() {
assert!(c.len() <= 512);
assert!(!c.is_empty());
assert_eq!(c.index_pos(), chunk_count * 512);
last_was_last = c.is_last();
chunk_count += 1;
}
assert_eq!(chunk_count, 20);
assert!(last_was_last);
}
}