use crate::logits::{StopSequence, TokenId};
use onnx_genai_kv::SequenceId;
use onnx_genai_ort::MtpDraftKvMode;
use onnx_genai_scheduler::{Priority, SchedulerConfig};
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MtpConfig {
pub head_model: PathBuf,
pub target_hidden_output: String,
pub embedding_weights: PathBuf,
pub lm_head_weights: PathBuf,
pub vocab_size: usize,
pub hidden_size: usize,
pub kv_mode: MtpDraftKvMode,
pub num_speculative_tokens: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SpeculativeMode {
None,
DraftModel,
PromptLookup {
ngram: usize,
max_tokens: usize,
},
Mtp(MtpConfig),
}
impl Default for SpeculativeMode {
fn default() -> Self {
Self::None
}
}
pub type SessionId = SequenceId;
#[derive(Debug, Clone)]
pub struct EngineConfig {
pub num_gpu_pages: usize,
pub page_size: usize,
pub scheduler: SchedulerConfig,
pub draft_model: Option<PathBuf>,
pub num_speculative_tokens: usize,
pub speculative_mode: SpeculativeMode,
}
impl Default for EngineConfig {
fn default() -> Self {
Self {
num_gpu_pages: 1024,
page_size: 16,
scheduler: SchedulerConfig::default(),
draft_model: None,
num_speculative_tokens: 4,
speculative_mode: SpeculativeMode::None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GeneratePrompt {
Text(String),
TokenIds(Vec<TokenId>),
}
impl From<String> for GeneratePrompt {
fn from(value: String) -> Self {
Self::Text(value)
}
}
impl From<&str> for GeneratePrompt {
fn from(value: &str) -> Self {
Self::Text(value.to_string())
}
}
impl From<Vec<TokenId>> for GeneratePrompt {
fn from(value: Vec<TokenId>) -> Self {
Self::TokenIds(value)
}
}
#[derive(Debug, Clone)]
pub struct GenerateOptions {
pub max_new_tokens: usize,
pub temperature: f32,
pub top_p: f32,
pub top_k: usize,
pub min_p: f32,
pub repetition_penalty: f32,
pub frequency_penalty: f32,
pub presence_penalty: f32,
pub greedy: bool,
pub stop_sequences: Vec<StopSequence>,
pub eos_token_id: Option<TokenId>,
pub stop_on_eos: bool,
pub max_context: Option<usize>,
pub num_speculative_tokens: Option<usize>,
pub speculative_mode: Option<SpeculativeMode>,
pub constraint: Option<GenerateConstraint>,
}
impl Default for GenerateOptions {
fn default() -> Self {
Self {
max_new_tokens: 128,
temperature: 1.0,
top_p: 1.0,
top_k: 0,
min_p: 0.0,
repetition_penalty: 1.0,
frequency_penalty: 0.0,
presence_penalty: 0.0,
greedy: true,
stop_sequences: Vec::new(),
eos_token_id: None,
stop_on_eos: true,
max_context: None,
num_speculative_tokens: None,
speculative_mode: None,
constraint: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GenerateConstraint {
Json,
JsonSchema(String),
Regex(String),
Lark(String),
}
impl GenerateOptions {
pub(crate) fn validate(&self) -> anyhow::Result<()> {
if self.max_new_tokens == 0 {
anyhow::bail!("max_new_tokens must be greater than zero");
}
if !self.temperature.is_finite() || self.temperature < 0.0 {
anyhow::bail!("temperature must be finite and non-negative");
}
if !self.top_p.is_finite() || self.top_p < 0.0 {
anyhow::bail!("top_p must be finite and non-negative");
}
if !self.min_p.is_finite() || !(0.0..=1.0).contains(&self.min_p) {
anyhow::bail!("min_p must be finite and between 0 and 1");
}
if !self.repetition_penalty.is_finite() || self.repetition_penalty <= 0.0 {
anyhow::bail!("repetition_penalty must be finite and greater than zero");
}
if !self.frequency_penalty.is_finite() {
anyhow::bail!("frequency_penalty must be finite");
}
if !self.presence_penalty.is_finite() {
anyhow::bail!("presence_penalty must be finite");
}
if self.max_context == Some(0) {
anyhow::bail!("max_context must be greater than zero when provided");
}
if self.num_speculative_tokens == Some(0) {
anyhow::bail!("num_speculative_tokens must be greater than zero when provided");
}
if let Some(SpeculativeMode::PromptLookup { ngram, max_tokens }) = &self.speculative_mode {
if *ngram == 0 {
anyhow::bail!("prompt-lookup ngram must be greater than zero");
}
if *max_tokens == 0 {
anyhow::bail!("prompt-lookup max_tokens must be greater than zero");
}
}
if let Some(SpeculativeMode::Mtp(config)) = &self.speculative_mode {
validate_mtp_config(config)?;
}
Ok(())
}
}
pub(crate) fn validate_mtp_config(config: &MtpConfig) -> anyhow::Result<()> {
if config.target_hidden_output.is_empty() {
anyhow::bail!("MTP target_hidden_output must not be empty");
}
if config.vocab_size == 0 || config.hidden_size == 0 {
anyhow::bail!("MTP vocab_size and hidden_size must be greater than zero");
}
if config.num_speculative_tokens == 0 {
anyhow::bail!("MTP num_speculative_tokens must be greater than zero");
}
Ok(())
}
#[derive(Debug, Clone)]
pub struct GenerateRequest {
pub prompt: GeneratePrompt,
pub options: GenerateOptions,
}
impl GenerateRequest {
pub fn new(prompt: impl Into<GeneratePrompt>) -> Self {
Self {
prompt: prompt.into(),
options: GenerateOptions::default(),
}
}
}
#[derive(Debug, Clone)]
pub struct PrioritizedGenerateRequest {
pub session_id: SessionId,
pub request: GenerateRequest,
pub priority: Priority,
}
#[derive(Debug, Clone)]
pub struct ScheduledGenerateArrival {
pub arrival_step: usize,
pub request: PrioritizedGenerateRequest,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PrioritizedGenerateResult {
pub session_id: SessionId,
pub result: GenerateResult,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FinishReason {
MaxTokens,
EosToken,
StopSequence { index: usize },
Length,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GenerateResult {
pub text: String,
pub token_ids: Vec<TokenId>,
pub finish_reason: FinishReason,
pub prefix_cache_hit_len: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GenerateToken {
pub token_id: TokenId,
pub text: String,
pub finish_reason: Option<FinishReason>,
}
pub type GenerateTokenCallback<'a> = dyn FnMut(GenerateToken) -> anyhow::Result<()> + Send + 'a;