llama_cpp_2/
speculative.rs1use std::ptr::NonNull;
4
5use crate::context::LlamaContext;
6use crate::llama_batch::LlamaBatch;
7use crate::status_is_ok;
8use crate::token::LlamaToken;
9
10#[derive(Clone, Copy, Debug, PartialEq)]
12pub struct MtpSpeculativeParams {
13 pub n_max: i32,
15 pub n_min: i32,
17 pub p_min: f32,
19}
20
21impl Default for MtpSpeculativeParams {
22 fn default() -> Self {
23 Self {
24 n_max: 3,
25 n_min: 0,
26 p_min: 0.0,
27 }
28 }
29}
30
31#[derive(Debug, Eq, PartialEq, thiserror::Error)]
33pub enum MtpSpeculativeError {
34 #[error("invalid MTP speculative parameters")]
36 InvalidParams,
37 #[error("llama.cpp failed to initialize MTP speculative decoding")]
39 InitFailed,
40 #[error("llama.cpp MTP speculative call failed with status {0}")]
42 Status(i32),
43 #[error("llama.cpp MTP draft exceeded configured maximum")]
45 DraftOverflow,
46}
47
48#[derive(Debug)]
53pub struct MtpSpeculative<'model> {
54 raw: NonNull<llama_cpp_sys_2::llama_rs_mtp_speculative>,
55 target_context: LlamaContext<'model>,
56 draft_context: LlamaContext<'model>,
57 n_max: usize,
58}
59
60impl<'model> MtpSpeculative<'model> {
61 pub fn new(
69 target_context: LlamaContext<'model>,
70 draft_context: LlamaContext<'model>,
71 params: MtpSpeculativeParams,
72 ) -> Result<Self, MtpSpeculativeError> {
73 if params.n_max <= 0 || params.n_min < 0 || params.n_min > params.n_max {
74 return Err(MtpSpeculativeError::InvalidParams);
75 }
76 let n_max =
77 usize::try_from(params.n_max).map_err(|_| MtpSpeculativeError::InvalidParams)?;
78
79 let raw = unsafe {
80 llama_cpp_sys_2::llama_rs_mtp_speculative_init(
81 target_context.context.as_ptr(),
82 draft_context.context.as_ptr(),
83 params.n_max,
84 params.n_min,
85 params.p_min,
86 )
87 };
88 let raw = NonNull::new(raw).ok_or(MtpSpeculativeError::InitFailed)?;
89
90 Ok(Self {
91 raw,
92 target_context,
93 draft_context,
94 n_max,
95 })
96 }
97
98 #[must_use]
100 pub fn target_context(&self) -> &LlamaContext<'model> {
101 &self.target_context
102 }
103
104 pub fn target_context_mut(&mut self) -> &mut LlamaContext<'model> {
106 &mut self.target_context
107 }
108
109 pub fn draft_context_mut(&mut self) -> &mut LlamaContext<'model> {
111 &mut self.draft_context
112 }
113
114 pub fn begin(&mut self, prompt_tokens: &[LlamaToken]) -> Result<(), MtpSpeculativeError> {
120 let prompt = tokens_to_raw(prompt_tokens);
121 let status = unsafe {
122 llama_cpp_sys_2::llama_rs_mtp_speculative_begin(
123 self.raw.as_ptr(),
124 prompt.as_ptr(),
125 prompt.len(),
126 )
127 };
128 status_to_result(status)
129 }
130
131 pub fn process(&mut self, batch: &LlamaBatch<'_>) -> Result<(), MtpSpeculativeError> {
139 let status = unsafe {
140 llama_cpp_sys_2::llama_rs_mtp_speculative_process(
141 self.raw.as_ptr(),
142 std::ptr::from_ref(&batch.llama_batch),
143 )
144 };
145 status_to_result(status)
146 }
147
148 pub fn draft(
155 &mut self,
156 n_past: i32,
157 id_last: LlamaToken,
158 prompt_tokens: &[LlamaToken],
159 ) -> Result<Vec<LlamaToken>, MtpSpeculativeError> {
160 if n_past < 0 {
161 return Err(MtpSpeculativeError::InvalidParams);
162 }
163
164 let prompt = tokens_to_raw(prompt_tokens);
165 let mut raw_out = vec![0; self.n_max];
166 let mut out_len = 0_usize;
167 let status = unsafe {
168 llama_cpp_sys_2::llama_rs_mtp_speculative_draft(
169 self.raw.as_ptr(),
170 n_past,
171 id_last.0,
172 prompt.as_ptr(),
173 prompt.len(),
174 raw_out.as_mut_ptr(),
175 raw_out.len(),
176 &raw mut out_len,
177 )
178 };
179 if status == llama_cpp_sys_2::LLAMA_RS_STATUS_ALLOCATION_FAILED {
180 return Err(MtpSpeculativeError::DraftOverflow);
181 }
182 status_to_result(status)?;
183 raw_out.truncate(out_len);
184 Ok(raw_out.into_iter().map(LlamaToken).collect())
185 }
186
187 pub fn accept(&mut self, n_accepted: u16) -> Result<(), MtpSpeculativeError> {
193 let status = unsafe {
194 llama_cpp_sys_2::llama_rs_mtp_speculative_accept(self.raw.as_ptr(), n_accepted)
195 };
196 status_to_result(status)
197 }
198}
199
200impl Drop for MtpSpeculative<'_> {
201 fn drop(&mut self) {
202 unsafe {
203 llama_cpp_sys_2::llama_rs_mtp_speculative_free(self.raw.as_ptr());
204 }
205 }
206}
207
208fn tokens_to_raw(tokens: &[LlamaToken]) -> Vec<llama_cpp_sys_2::llama_token> {
209 tokens.iter().map(|token| token.0).collect()
210}
211
212fn status_to_result(status: llama_cpp_sys_2::llama_rs_status) -> Result<(), MtpSpeculativeError> {
213 if status_is_ok(status) {
214 Ok(())
215 } else {
216 Err(MtpSpeculativeError::Status(status as i32))
217 }
218}