use std::ffi::{CStr, CString};
use std::path::Path;
use std::sync::Arc;
use crate::model::LlamaModel;
#[derive(Debug, Clone, Copy)]
pub struct MtpSampling {
pub temperature: f32,
pub top_k: i32,
pub top_p: f32,
pub repeat_penalty: f32,
pub seed: u32,
}
impl Default for MtpSampling {
fn default() -> Self {
Self {
temperature: 0.8,
top_k: 40,
top_p: 0.95,
repeat_penalty: 1.0,
seed: 0xFFFF_FFFF, }
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct MtpStats {
pub n_prompt_tokens: i32,
pub n_generated: i32,
pub n_drafted: i32,
pub n_accepted: i32,
}
impl MtpStats {
#[must_use]
pub fn acceptance_rate(&self) -> f32 {
if self.n_drafted <= 0 {
0.0
} else {
self.n_accepted as f32 / self.n_drafted as f32
}
}
}
#[derive(Debug, Clone)]
pub struct MtpGeneration {
pub text: String,
pub stats: MtpStats,
}
impl MtpGeneration {
#[must_use]
pub fn acceptance_rate(&self) -> f32 {
self.stats.acceptance_rate()
}
#[must_use]
pub fn n_prompt_tokens(&self) -> i32 {
self.stats.n_prompt_tokens
}
#[must_use]
pub fn n_generated(&self) -> i32 {
self.stats.n_generated
}
}
#[derive(Debug, Clone)]
pub struct MtpStep {
pub piece: String,
pub done: bool,
}
#[derive(Debug, thiserror::Error)]
pub enum MtpError {
#[error("{0}")]
NulError(#[from] std::ffi::NulError),
#[error("failed to load the MTP draft model GGUF")]
ModelLoadFailed,
#[error("failed to initialize MTP speculative engine (context or MTP init failed)")]
InitFailed,
#[error("failed to begin MTP generation (bad prompt or decode error)")]
BeginFailed,
#[error("MTP speculative step failed")]
StepFailed,
#[error("{0}")]
Utf8(#[from] std::str::Utf8Error),
}
pub struct LlamaMtpModel {
handle: *mut infrastructure_llama_bindings::ewe_mtp_model,
}
unsafe impl Send for LlamaMtpModel {}
unsafe impl Sync for LlamaMtpModel {}
impl LlamaMtpModel {
pub fn load(draft_path: &Path) -> Result<Self, MtpError> {
let draft = CString::new(draft_path.to_string_lossy().as_bytes())?;
let handle = unsafe { infrastructure_llama_bindings::ewe_mtp_model_load(draft.as_ptr()) };
if handle.is_null() {
return Err(MtpError::ModelLoadFailed);
}
Ok(Self { handle })
}
}
impl Drop for LlamaMtpModel {
fn drop(&mut self) {
unsafe { infrastructure_llama_bindings::ewe_mtp_model_free(self.handle) };
}
}
pub struct LlamaMtp {
handle: *mut infrastructure_llama_bindings::ewe_mtp,
_draft: Arc<LlamaMtpModel>,
}
unsafe impl Send for LlamaMtp {}
impl LlamaMtp {
pub fn new(
target: &LlamaModel,
draft: Arc<LlamaMtpModel>,
n_ctx: u32,
n_batch: u32,
n_threads: i32,
n_draft_max: i32,
) -> Result<Self, MtpError> {
let handle = unsafe {
infrastructure_llama_bindings::ewe_mtp_init(
target.model.as_ptr(),
draft.handle,
n_ctx,
n_batch,
n_threads,
n_draft_max,
)
};
if handle.is_null() {
return Err(MtpError::InitFailed);
}
Ok(Self {
handle,
_draft: draft,
})
}
pub fn begin(
&self,
prompt: &str,
n_predict: i32,
sampling: MtpSampling,
) -> Result<i32, MtpError> {
let c_prompt = CString::new(prompt)?;
let c_sampling = infrastructure_llama_bindings::ewe_mtp_sampling {
temperature: sampling.temperature,
top_k: sampling.top_k,
top_p: sampling.top_p,
repeat_penalty: sampling.repeat_penalty,
seed: sampling.seed,
};
let n = unsafe {
infrastructure_llama_bindings::ewe_mtp_begin(
self.handle,
c_prompt.as_ptr(),
n_predict,
c_sampling,
)
};
if n < 0 {
return Err(MtpError::BeginFailed);
}
Ok(n)
}
pub fn step(&self) -> Result<MtpStep, MtpError> {
let mut out_piece: *mut std::os::raw::c_char = std::ptr::null_mut();
let rc = unsafe {
infrastructure_llama_bindings::ewe_mtp_step(self.handle, &mut out_piece)
};
if rc < 0 {
if !out_piece.is_null() {
unsafe { infrastructure_llama_bindings::ewe_chat_string_free(out_piece) };
}
return Err(MtpError::StepFailed);
}
let piece = if out_piece.is_null() {
String::new()
} else {
let s = unsafe { CStr::from_ptr(out_piece) }
.to_str()
.map(std::borrow::ToOwned::to_owned);
unsafe { infrastructure_llama_bindings::ewe_chat_string_free(out_piece) };
s?
};
Ok(MtpStep {
piece,
done: rc == 0,
})
}
#[must_use]
pub fn stats(&self) -> MtpStats {
let mut s = MtpStats::default();
unsafe {
infrastructure_llama_bindings::ewe_mtp_stats(
self.handle,
&mut s.n_prompt_tokens,
&mut s.n_generated,
&mut s.n_drafted,
&mut s.n_accepted,
);
}
s
}
pub fn generate(
&self,
prompt: &str,
n_predict: i32,
sampling: MtpSampling,
) -> Result<MtpGeneration, MtpError> {
self.begin(prompt, n_predict, sampling)?;
let mut text = String::new();
loop {
let step = self.step()?;
text.push_str(&step.piece);
if step.done {
break;
}
}
Ok(MtpGeneration {
text,
stats: self.stats(),
})
}
}
impl Drop for LlamaMtp {
fn drop(&mut self) {
unsafe { infrastructure_llama_bindings::ewe_mtp_free(self.handle) };
}
}