use std::ptr::NonNull;
use crate::context::LlamaContext;
use crate::llama_batch::LlamaBatch;
use crate::status_is_ok;
use crate::token::LlamaToken;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct MtpSpeculativeParams {
pub n_max: i32,
pub n_min: i32,
pub p_min: f32,
}
impl Default for MtpSpeculativeParams {
fn default() -> Self {
Self {
n_max: 3,
n_min: 0,
p_min: 0.0,
}
}
}
#[derive(Debug, Eq, PartialEq, thiserror::Error)]
pub enum MtpSpeculativeError {
#[error("invalid MTP speculative parameters")]
InvalidParams,
#[error("llama.cpp failed to initialize MTP speculative decoding")]
InitFailed,
#[error("llama.cpp MTP speculative call failed with status {0}")]
Status(i32),
#[error("llama.cpp MTP draft exceeded configured maximum")]
DraftOverflow,
}
#[derive(Debug)]
pub struct MtpSpeculative<'model> {
raw: NonNull<llama_cpp_sys_2::llama_rs_mtp_speculative>,
target_context: LlamaContext<'model>,
draft_context: LlamaContext<'model>,
n_max: usize,
}
impl<'model> MtpSpeculative<'model> {
pub fn new(
target_context: LlamaContext<'model>,
draft_context: LlamaContext<'model>,
params: MtpSpeculativeParams,
) -> Result<Self, MtpSpeculativeError> {
if params.n_max <= 0 || params.n_min < 0 || params.n_min > params.n_max {
return Err(MtpSpeculativeError::InvalidParams);
}
let n_max =
usize::try_from(params.n_max).map_err(|_| MtpSpeculativeError::InvalidParams)?;
let raw = unsafe {
llama_cpp_sys_2::llama_rs_mtp_speculative_init(
target_context.context.as_ptr(),
draft_context.context.as_ptr(),
params.n_max,
params.n_min,
params.p_min,
)
};
let raw = NonNull::new(raw).ok_or(MtpSpeculativeError::InitFailed)?;
Ok(Self {
raw,
target_context,
draft_context,
n_max,
})
}
#[must_use]
pub fn target_context(&self) -> &LlamaContext<'model> {
&self.target_context
}
pub fn target_context_mut(&mut self) -> &mut LlamaContext<'model> {
&mut self.target_context
}
pub fn draft_context_mut(&mut self) -> &mut LlamaContext<'model> {
&mut self.draft_context
}
pub fn begin(&mut self, prompt_tokens: &[LlamaToken]) -> Result<(), MtpSpeculativeError> {
let prompt = tokens_to_raw(prompt_tokens);
let status = unsafe {
llama_cpp_sys_2::llama_rs_mtp_speculative_begin(
self.raw.as_ptr(),
prompt.as_ptr(),
prompt.len(),
)
};
status_to_result(status)
}
pub fn process(&mut self, batch: &LlamaBatch<'_>) -> Result<(), MtpSpeculativeError> {
let status = unsafe {
llama_cpp_sys_2::llama_rs_mtp_speculative_process(
self.raw.as_ptr(),
std::ptr::from_ref(&batch.llama_batch),
)
};
status_to_result(status)
}
pub fn draft(
&mut self,
n_past: i32,
id_last: LlamaToken,
prompt_tokens: &[LlamaToken],
) -> Result<Vec<LlamaToken>, MtpSpeculativeError> {
if n_past < 0 {
return Err(MtpSpeculativeError::InvalidParams);
}
let prompt = tokens_to_raw(prompt_tokens);
let mut raw_out = vec![0; self.n_max];
let mut out_len = 0_usize;
let status = unsafe {
llama_cpp_sys_2::llama_rs_mtp_speculative_draft(
self.raw.as_ptr(),
n_past,
id_last.0,
prompt.as_ptr(),
prompt.len(),
raw_out.as_mut_ptr(),
raw_out.len(),
&raw mut out_len,
)
};
if status == llama_cpp_sys_2::LLAMA_RS_STATUS_ALLOCATION_FAILED {
return Err(MtpSpeculativeError::DraftOverflow);
}
status_to_result(status)?;
raw_out.truncate(out_len);
Ok(raw_out.into_iter().map(LlamaToken).collect())
}
pub fn accept(&mut self, n_accepted: u16) -> Result<(), MtpSpeculativeError> {
let status = unsafe {
llama_cpp_sys_2::llama_rs_mtp_speculative_accept(self.raw.as_ptr(), n_accepted)
};
status_to_result(status)
}
}
impl Drop for MtpSpeculative<'_> {
fn drop(&mut self) {
unsafe {
llama_cpp_sys_2::llama_rs_mtp_speculative_free(self.raw.as_ptr());
}
}
}
fn tokens_to_raw(tokens: &[LlamaToken]) -> Vec<llama_cpp_sys_2::llama_token> {
tokens.iter().map(|token| token.0).collect()
}
fn status_to_result(status: llama_cpp_sys_2::llama_rs_status) -> Result<(), MtpSpeculativeError> {
if status_is_ok(status) {
Ok(())
} else {
Err(MtpSpeculativeError::Status(status as i32))
}
}