1use std::collections::{BTreeSet, VecDeque};
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
34pub struct KvPageId(pub u32);
35
36#[derive(Debug, Clone, Copy)]
39pub struct KvPageDesc {
40 pub offset: usize,
42 pub bytes: usize,
44 pub filled: u16,
47}
48
49pub struct KvPagePool {
56 free: BTreeSet<u32>,
58 descs: Vec<KvPageDesc>,
60 pub bytes_per_page: usize,
62 pub tokens_per_page: u16,
63}
64
65impl KvPagePool {
66 pub fn new(num_pages: u32, bytes_per_page: usize, tokens_per_page: u16) -> Self {
67 let descs: Vec<KvPageDesc> = (0..num_pages)
68 .map(|i| KvPageDesc {
69 offset: (i as usize) * bytes_per_page,
70 bytes: bytes_per_page,
71 filled: 0,
72 })
73 .collect();
74 let free: BTreeSet<u32> = (0..num_pages).collect();
75 Self {
76 free,
77 descs,
78 bytes_per_page,
79 tokens_per_page,
80 }
81 }
82
83 pub fn capacity(&self) -> u32 {
84 self.descs.len() as u32
85 }
86 pub fn free_count(&self) -> u32 {
87 self.free.len() as u32
88 }
89 pub fn used_count(&self) -> u32 {
90 self.capacity() - self.free_count()
91 }
92
93 pub fn alloc(&mut self) -> Option<KvPageId> {
95 let id = *self.free.iter().next()?;
96 self.free.remove(&id);
97 self.descs[id as usize].filled = 0;
99 Some(KvPageId(id))
100 }
101
102 pub fn free(&mut self, id: KvPageId) {
103 self.free.insert(id.0);
104 }
105
106 pub fn descriptor(&self, id: KvPageId) -> &KvPageDesc {
107 &self.descs[id.0 as usize]
108 }
109
110 pub fn descriptor_mut(&mut self, id: KvPageId) -> &mut KvPageDesc {
111 &mut self.descs[id.0 as usize]
112 }
113}
114
115#[derive(Debug, Clone, Default)]
119pub struct KvBlockTable {
120 pages: Vec<KvPageId>,
121 pub seq_len: u32,
123}
124
125impl KvBlockTable {
126 pub fn new() -> Self {
127 Self::default()
128 }
129
130 pub fn push_page(&mut self, page: KvPageId) {
133 self.pages.push(page);
134 }
135
136 pub fn page_for_token(&self, t: u32, tokens_per_page: u16) -> Option<KvPageId> {
139 let idx = (t / tokens_per_page as u32) as usize;
140 self.pages.get(idx).copied()
141 }
142
143 pub fn page_count(&self) -> usize {
144 self.pages.len()
145 }
146
147 pub fn release(&mut self, pool: &mut KvPagePool) {
150 for p in self.pages.drain(..) {
151 pool.free(p);
152 }
153 self.seq_len = 0;
154 }
155
156 pub fn pages(&self) -> &[KvPageId] {
158 &self.pages
159 }
160}
161
162#[derive(Debug, Clone)]
166pub struct BatchEntry {
167 pub seq_id: u64,
168 pub kind: BatchKind,
169 pub input_tokens: Vec<u32>,
171 pub cached_len: u32,
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176pub enum BatchKind {
177 Prefill,
180 Decode,
182}
183
184pub struct BatchConstructor {
190 pub max_tokens_per_batch: usize,
192 pub max_entries: usize,
195}
196
197impl BatchConstructor {
198 pub fn new(max_tokens_per_batch: usize, max_entries: usize) -> Self {
199 Self {
200 max_tokens_per_batch,
201 max_entries,
202 }
203 }
204
205 pub fn build(
210 &self,
211 decode_queue: &mut VecDeque<BatchEntry>,
212 prefill_queue: &mut VecDeque<BatchEntry>,
213 ) -> Vec<BatchEntry> {
214 let mut batch: Vec<BatchEntry> = Vec::new();
215 let mut tokens_used = 0usize;
216
217 while batch.len() < self.max_entries {
218 if let Some(d) = decode_queue.front() {
219 let need = d.input_tokens.len();
220 if tokens_used + need > self.max_tokens_per_batch {
221 break;
222 }
223 batch.push(decode_queue.pop_front().unwrap());
224 tokens_used += need;
225 } else {
226 break;
227 }
228 }
229
230 while batch.len() < self.max_entries {
231 let want = match prefill_queue.front() {
232 Some(p) => p.input_tokens.len(),
233 None => break,
234 };
235 let remaining = self.max_tokens_per_batch.saturating_sub(tokens_used);
236 if remaining == 0 {
237 break;
238 }
239
240 if want <= remaining {
241 batch.push(prefill_queue.pop_front().unwrap());
242 tokens_used += want;
243 } else {
244 let mut p = prefill_queue.pop_front().unwrap();
247 let chunk: Vec<u32> = p.input_tokens.drain(..remaining).collect();
248 let chunk_entry = BatchEntry {
249 seq_id: p.seq_id,
250 kind: BatchKind::Prefill,
251 input_tokens: chunk,
252 cached_len: p.cached_len,
253 };
254 p.cached_len += remaining as u32;
255 batch.push(chunk_entry);
256 prefill_queue.push_front(p);
257 break;
258 }
259 }
260
261 batch
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268
269 #[test]
270 fn pool_alloc_free_round_trip() {
271 let mut pool = KvPagePool::new(4, 1024, 16);
272 assert_eq!(pool.free_count(), 4);
273 let p1 = pool.alloc().unwrap();
274 let p2 = pool.alloc().unwrap();
275 assert_eq!(pool.free_count(), 2);
276 pool.free(p1);
277 pool.free(p2);
278 assert_eq!(pool.free_count(), 4);
279 }
280
281 #[test]
282 fn pool_returns_none_when_exhausted() {
283 let mut pool = KvPagePool::new(2, 64, 4);
284 let _a = pool.alloc().unwrap();
285 let _b = pool.alloc().unwrap();
286 assert!(pool.alloc().is_none());
287 }
288
289 #[test]
290 fn pool_descriptor_offsets_are_unique_and_aligned() {
291 let pool = KvPagePool::new(4, 256, 16);
292 for i in 0..4u32 {
293 let d = pool.descriptor(KvPageId(i));
294 assert_eq!(d.offset, i as usize * 256);
295 assert_eq!(d.bytes, 256);
296 }
297 }
298
299 #[test]
300 fn block_table_page_for_token() {
301 let mut pool = KvPagePool::new(8, 64, 4);
302 let mut bt = KvBlockTable::new();
303 for _ in 0..3 {
304 bt.push_page(pool.alloc().unwrap());
305 }
306 assert_eq!(bt.page_for_token(0, 4), Some(bt.pages()[0]));
308 assert_eq!(bt.page_for_token(7, 4), Some(bt.pages()[1]));
309 assert_eq!(bt.page_for_token(11, 4), Some(bt.pages()[2]));
310 assert_eq!(bt.page_for_token(12, 4), None);
311 }
312
313 #[test]
314 fn block_table_release_returns_pages() {
315 let mut pool = KvPagePool::new(8, 64, 4);
316 let mut bt = KvBlockTable::new();
317 for _ in 0..3 {
318 bt.push_page(pool.alloc().unwrap());
319 }
320 assert_eq!(pool.free_count(), 5);
321 bt.release(&mut pool);
322 assert_eq!(pool.free_count(), 8);
323 assert_eq!(bt.page_count(), 0);
324 }
325
326 #[test]
327 fn batch_constructor_decodes_first_then_prefill() {
328 let bc = BatchConstructor::new(8, 16);
329 let mut decodes: VecDeque<BatchEntry> = (0..3)
330 .map(|i| BatchEntry {
331 seq_id: i,
332 kind: BatchKind::Decode,
333 input_tokens: vec![100 + i as u32],
334 cached_len: 50,
335 })
336 .collect();
337 let mut prefills: VecDeque<BatchEntry> = (0..2)
338 .map(|i| BatchEntry {
339 seq_id: 100 + i,
340 kind: BatchKind::Prefill,
341 input_tokens: vec![1; 3],
342 cached_len: 0,
343 })
344 .collect();
345
346 let batch = bc.build(&mut decodes, &mut prefills);
347 assert_eq!(batch.len(), 5);
352 for entry in batch.iter().take(3) {
354 assert_eq!(entry.kind, BatchKind::Decode);
355 }
356 for entry in batch.iter().skip(3).take(2) {
357 assert_eq!(entry.kind, BatchKind::Prefill);
358 }
359 let total_tokens: usize = batch.iter().map(|e| e.input_tokens.len()).sum();
360 assert_eq!(total_tokens, 8);
361
362 assert_eq!(prefills.len(), 1);
364 assert_eq!(prefills[0].input_tokens.len(), 1);
365 assert_eq!(prefills[0].cached_len, 2);
366 }
367
368 #[test]
369 fn batch_constructor_respects_max_entries() {
370 let bc = BatchConstructor::new(1024, 2); let mut decodes: VecDeque<BatchEntry> = (0..5)
372 .map(|i| BatchEntry {
373 seq_id: i,
374 kind: BatchKind::Decode,
375 input_tokens: vec![1],
376 cached_len: 0,
377 })
378 .collect();
379 let mut prefills: VecDeque<BatchEntry> = VecDeque::new();
380 let batch = bc.build(&mut decodes, &mut prefills);
381 assert_eq!(batch.len(), 2);
382 assert_eq!(decodes.len(), 3);
383 }
384}