Skip to main content

babichjacob_llama_cpp_2/
llama_batch.rs

1//! Safe wrapper around `llama_batch`.
2
3use crate::token::LlamaToken;
4use llama_cpp_sys_2::{llama_batch, llama_batch_free, llama_batch_init, llama_pos, llama_seq_id};
5
6/// A safe wrapper around `llama_batch`.
7#[derive(Debug)]
8pub struct LlamaBatch {
9    /// The number of tokens the batch was allocated with. they are safe to write to - but not necessarily read from as they are not necessarily initialized
10    allocated: usize,
11    /// The logits that are initialized. Used by [`LlamaContext`] to ensure that only initialized logits are accessed.
12    pub(crate) initialized_logits: Vec<i32>,
13    /// The llama_cpp batch. always initialize by `llama_cpp_sys_2::llama_batch_init(allocated, <unknown>, <unknown>)`
14    pub(crate) llama_batch: llama_batch,
15}
16
17/// Errors that can occur when adding a token to a batch.
18#[derive(thiserror::Error, Debug, PartialEq, Eq)]
19pub enum BatchAddError {
20    /// There was not enough space in the batch to add the token.
21    #[error("Insufficient Space of {0}")]
22    InsufficientSpace(usize),
23    /// Empty buffer is provided for get_one
24    #[error("Empty buffer")]
25    EmptyBuffer,
26}
27
28impl LlamaBatch {
29    /// Clear the batch. This does not free the memory associated with the batch, but it does reset
30    /// the number of tokens to 0.
31    pub fn clear(&mut self) {
32        self.llama_batch.n_tokens = 0;
33        self.initialized_logits.clear();
34    }
35
36    /// add a token to the batch for sequences `seq_ids` at position `pos`. If `logits` is true, the
37    /// token will be initialized and can be read from after the next decode.
38    ///
39    /// # Panics
40    ///
41    /// - [`self.llama_batch.n_tokens`] does not fit into a usize
42    /// - [`seq_ids.len()`] does not fit into a [`llama_seq_id`]
43    ///
44    /// # Errors
45    ///
46    /// returns a error if there is insufficient space in the buffer
47    pub fn add(
48        &mut self,
49        LlamaToken(id): LlamaToken,
50        pos: llama_pos,
51        seq_ids: &[i32],
52        logits: bool,
53    ) -> Result<(), BatchAddError> {
54        if self.allocated
55            < usize::try_from(self.n_tokens() + 1).expect("cannot fit n_tokens into a usize")
56        {
57            return Err(BatchAddError::InsufficientSpace(self.allocated));
58        }
59        let offset = self.llama_batch.n_tokens;
60        let offset_usize = usize::try_from(offset).expect("cannot fit n_tokens into a usize");
61        unsafe {
62            // batch.token   [batch.n_tokens] = id;
63            self.llama_batch.token.add(offset_usize).write(id);
64            // batch.pos     [batch.n_tokens] = pos,
65            self.llama_batch.pos.add(offset_usize).write(pos);
66            // batch.n_seq_id[batch.n_tokens] = seq_ids.size();
67            self.llama_batch.n_seq_id.add(offset_usize).write(
68                llama_seq_id::try_from(seq_ids.len())
69                    .expect("cannot fit seq_ids.len() into a llama_seq_id"),
70            );
71            // for (size_t i = 0; i < seq_ids.size(); ++i) {
72            //     batch.seq_id[batch.n_tokens][i] = seq_ids[i];
73            // }
74            for (i, seq_id) in seq_ids.iter().enumerate() {
75                let tmp = *self.llama_batch.seq_id.add(offset_usize);
76                tmp.add(i).write(*seq_id);
77            }
78            // batch.logits  [batch.n_tokens] = logits;
79            self.llama_batch
80                .logits
81                .add(offset_usize)
82                .write(i8::from(logits));
83        }
84
85        if logits {
86            self.initialized_logits.push(offset);
87        } else {
88            self.initialized_logits.retain(|l| l != &offset);
89        }
90
91        // batch.n_tokens++;
92        self.llama_batch.n_tokens += 1;
93
94        Ok(())
95    }
96
97    /// Add a sequence of tokens to the batch for the given sequence id. If `logits_all` is true, the
98    /// tokens will be initialized and can be read from after the next decode.
99    ///
100    /// Either way the last token in the sequence will have its logits set to `true`.
101    ///
102    /// # Errors
103    ///
104    /// Returns an error if there is insufficient space in the buffer
105    ///
106    /// # Panics
107    ///
108    /// - [`self.llama_batch.n_tokens`] does not fit into a [`usize`]
109    /// - [`n_tokens - 1`] does not fit into a [`llama_pos`]
110    pub fn add_sequence(
111        &mut self,
112        tokens: &[LlamaToken],
113        seq_id: i32,
114        logits_all: bool,
115    ) -> Result<(), BatchAddError> {
116        let n_tokens_0 =
117            usize::try_from(self.llama_batch.n_tokens).expect("cannot fit n_tokens into a usize");
118        let n_tokens = tokens.len();
119
120        if self.allocated < n_tokens_0 + n_tokens {
121            return Err(BatchAddError::InsufficientSpace(self.allocated));
122        }
123
124        let last_index = llama_pos::try_from(n_tokens.saturating_sub(1))
125            .expect("cannot fit n_tokens into a llama_pos");
126        for (i, token) in (0..).zip(tokens.iter()) {
127            self.add(*token, i, &[seq_id], logits_all || i == last_index)?;
128        }
129
130        Ok(())
131    }
132
133    /// Create a new `LlamaBatch` that can contain up to `n_tokens` tokens.
134    ///
135    /// # Arguments
136    ///
137    /// - `n_tokens`: the maximum number of tokens that can be added to the batch
138    /// - `n_seq_max`: the maximum number of sequences that can be added to the batch (generally 1 unless you know what you are doing)
139    ///
140    /// # Panics
141    ///
142    /// Panics if `n_tokens` is greater than `i32::MAX`.
143    #[must_use]
144    pub fn new(n_tokens: usize, n_seq_max: i32) -> Self {
145        let n_tokens_i32 = i32::try_from(n_tokens).expect("cannot fit n_tokens into a i32");
146        let batch = unsafe { llama_batch_init(n_tokens_i32, 0, n_seq_max) };
147
148        LlamaBatch {
149            allocated: n_tokens,
150            initialized_logits: vec![],
151            llama_batch: batch,
152        }
153    }
154
155    /// llama_batch_get_one
156    /// Return batch for single sequence of tokens starting at pos_0
157    ///
158    /// NOTE: this is a helper function to facilitate transition to the new batch API
159    ///
160    pub fn get_one(tokens: &[LlamaToken]) -> Result<Self, BatchAddError> {
161        if tokens.is_empty() {
162            return Err(BatchAddError::EmptyBuffer);
163        }
164        let batch = unsafe {
165            let ptr = tokens.as_ptr() as *mut i32;
166            llama_cpp_sys_2::llama_batch_get_one(ptr, tokens.len() as i32)
167        };
168        let batch = Self {
169            allocated: 0,
170            initialized_logits: vec![(tokens.len() - 1) as i32],
171            llama_batch: batch,
172        };
173        Ok(batch)
174    }
175
176    /// Returns the number of tokens in the batch.
177    #[must_use]
178    pub fn n_tokens(&self) -> i32 {
179        self.llama_batch.n_tokens
180    }
181}
182
183impl Drop for LlamaBatch {
184    /// Drops the `LlamaBatch`.
185    ///
186    /// ```
187    /// # use llama_cpp_2::llama_batch::LlamaBatch;
188    /// # use std::error::Error;
189    /// # fn main() -> Result<(), Box<dyn Error>> {
190    /// let batch = LlamaBatch::new(512, 1);
191    /// // frees the memory associated with the batch. (allocated by llama.cpp)
192    /// drop(batch);
193    /// # Ok(())
194    /// # }
195    fn drop(&mut self) {
196        unsafe {
197            if self.allocated > 0 {
198                llama_batch_free(self.llama_batch);
199            }
200        }
201    }
202}