use std::collections::BTreeSet;
use ts_wire::{
codec::Dict,
output::{FinishDetails, PromptTopLogprobs, TopLogprobRows, TopLogprobs},
value::Value,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AbortCode {
TransferFailed = 521,
UnknownError = 522,
NumericalError = 523,
CapacityAbort = 524,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FinishReason {
Length(i64),
MatchedToken(i32),
Abort {
message: String,
err_type: AbortCode,
status_code: Option<u16>,
},
}
impl FinishReason {
pub fn abort(message: impl Into<String>) -> Self {
FinishReason::Abort {
message: message.into(),
err_type: AbortCode::UnknownError,
status_code: None,
}
}
pub fn wire_type(&self) -> &'static str {
match self {
FinishReason::Length(_) => "length",
FinishReason::MatchedToken(_) => "stop",
FinishReason::Abort { .. } => "abort",
}
}
pub fn abort_message(&self) -> Option<&str> {
match self {
FinishReason::Abort { message, .. } => Some(message),
_ => None,
}
}
pub fn details(&self) -> FinishDetails {
let entry = |key: &str, value: Value| (key.to_owned(), value);
let text = |value: &str| Value::Str(value.to_owned());
Dict(match self {
FinishReason::Length(length) => vec![
entry("type", text("length")),
entry("length", Value::from(*length)),
],
FinishReason::MatchedToken(token) => vec![
entry("type", text("stop")),
entry("matched", Value::from(i64::from(*token))),
],
FinishReason::Abort {
message,
err_type,
status_code,
} => {
let mut entries = vec![
entry("type", text("abort")),
entry("message", text(message)),
entry("err_type", Value::from(*err_type as i64)),
];
if let Some(code) = status_code {
entries.push(entry("status_code", Value::from(i64::from(*code))));
}
entries
}
})
}
}
type Alternatives = (Vec<f64>, Vec<i32>);
#[derive(Debug, Clone, PartialEq)]
pub struct InputLogprobs {
pub values: Vec<Option<f64>>,
pub ids: Vec<i32>,
pub top: Option<PromptTopLogprobs>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LogprobRequest {
pub start: i64,
pub top_logprobs_num: usize,
pub computes_prompt_logprobs: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StopCriteria {
pub stop_token_ids: Vec<i32>,
pub token_limit: i64,
}
pub const MAX_STOP_TOKEN_IDS: usize = 16;
#[derive(Debug, Clone)]
pub struct RequestState {
pub prompt_ids: Vec<i32>,
pub prompt_len: i64,
pub max_new_tokens: i64,
pub stop_token_ids: BTreeSet<i32>,
pub stream: bool,
pub stream_interval: Option<i64>,
pub return_logprob: bool,
pub logprob_start_len: i64,
pub top_logprobs_num: usize,
pub returns_input_logprobs: bool,
pub computed_length: i64,
pub output_ids: Vec<i32>,
pub output_logprobs: Vec<f64>,
pub output_top_logprobs: Option<TopLogprobs>,
pub input_logprob_rows: Option<Vec<Option<f64>>>,
pub input_top_logprob_rows: Option<Vec<Option<Alternatives>>>,
pub input_logprobs: Option<InputLogprobs>,
pub input_logprobs_sent: bool,
pub cached_tokens: i64,
pub spec_verify_ct: i64,
pub accept_draft_tokens: Option<f64>,
pub numerical_error_detected: bool,
pub finished_reason: Option<FinishReason>,
pub to_abort: bool,
pub abort_notify_client: bool,
pub submitted_at: Option<std::time::Instant>,
pub created_time: f64,
pub timings: Option<crate::request_stats::RequestTimings>,
pub grammar: Option<RequestGrammar>,
pub admission: u64,
pub send_token_offset: usize,
pub finished_output: bool,
}
#[derive(Clone)]
pub struct RequestGrammar {
pub handle: u64,
pub matcher: ts_grammar::table::SharedMatcher,
}
impl std::fmt::Debug for RequestGrammar {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RequestGrammar")
.field("handle", &self.handle)
.finish_non_exhaustive()
}
}
pub const FORCED_STREAM_INTERVAL: i64 = 50;
impl RequestState {
pub fn new(
prompt_ids: Vec<i32>,
max_new_tokens: i64,
stop_token_ids: BTreeSet<i32>,
stream: bool,
stream_interval: Option<i64>,
return_logprob: bool,
) -> Self {
let prompt_len = prompt_ids.len() as i64;
Self {
prompt_len,
prompt_ids,
max_new_tokens,
stop_token_ids,
stream,
stream_interval,
return_logprob,
logprob_start_len: (prompt_len - 1).max(0),
top_logprobs_num: 0,
returns_input_logprobs: false,
computed_length: 0,
output_ids: Vec::new(),
output_logprobs: Vec::new(),
output_top_logprobs: None,
input_logprob_rows: None,
input_top_logprob_rows: None,
input_logprobs: None,
input_logprobs_sent: false,
cached_tokens: 0,
spec_verify_ct: 0,
accept_draft_tokens: None,
numerical_error_detected: false,
finished_reason: None,
to_abort: false,
abort_notify_client: false,
submitted_at: None,
created_time: 0.0,
timings: None,
grammar: None,
admission: 0,
send_token_offset: 0,
finished_output: false,
}
}
pub fn set_logprobs(&mut self, request: LogprobRequest) {
let last_position = (self.prompt_len - 1).max(0);
self.logprob_start_len = if request.start < 0 {
last_position
} else {
request.start.min(last_position)
};
self.top_logprobs_num = request.top_logprobs_num;
self.returns_input_logprobs = self.return_logprob && request.computes_prompt_logprobs;
let scored = (last_position - self.logprob_start_len) as usize;
if self.wants_input_logprobs() {
self.input_logprob_rows = Some(vec![None; scored]);
if self.top_logprobs_num > 0 {
self.input_top_logprob_rows = Some(vec![None; scored]);
}
}
if self.return_logprob && self.top_logprobs_num > 0 {
self.output_top_logprobs = Some((Vec::new(), Vec::new()));
}
}
pub fn wants_input_logprobs(&self) -> bool {
self.returns_input_logprobs && self.logprob_start_len < self.prompt_len - 1
}
pub fn record_input_logprobs(
&mut self,
start: i64,
logprobs: &[f64],
top: Option<TopLogprobRows<'_>>,
) -> Result<(), String> {
let rows = self
.input_logprob_rows
.as_mut()
.ok_or("prompt logprobs recorded for a request that wants none")?;
let offset = start - self.logprob_start_len;
if offset < 0 || offset as usize + logprobs.len() > rows.len() {
return Err(format!(
"prompt logprob rows [{start}, {}) fall outside [{}, {})",
start + logprobs.len() as i64,
self.logprob_start_len,
self.prompt_len - 1
));
}
let offset = offset as usize;
for (row, logprob) in rows[offset..].iter_mut().zip(logprobs) {
*row = Some(*logprob);
}
if let Some(top_rows) = self.input_top_logprob_rows.as_mut() {
let (values, ids) = top.ok_or(
"prompt logprobs recorded without the top-k alternatives the request asked for",
)?;
let k = self.top_logprobs_num;
for (row, (row_values, row_ids)) in
top_rows[offset..].iter_mut().zip(values.iter().zip(ids))
{
*row = Some((
row_values[..k.min(row_values.len())].to_vec(),
row_ids[..k.min(row_ids.len())].to_vec(),
));
}
}
Ok(())
}
pub fn finalize_input_logprobs(&mut self) -> Result<(), String> {
if !self.returns_input_logprobs || self.input_logprobs.is_some() {
return Ok(());
}
let rows = self.input_logprob_rows.take().unwrap_or_default();
let missing = rows.iter().filter(|row| row.is_none()).count();
if missing > 0 {
return Err(format!(
"prompt logprobs incomplete: {missing} of {} positions have no value",
rows.len()
));
}
let start = self.logprob_start_len as usize;
let mut values = Vec::with_capacity(rows.len() + 1);
values.push(None);
values.extend(rows);
let ids = self.prompt_ids[start..].to_vec();
if values.len() != ids.len() {
return Err(format!(
"prompt logprobs have {} values for {} tokens",
values.len(),
ids.len()
));
}
let top = if self.top_logprobs_num > 0 {
let tops = self.input_top_logprob_rows.take().unwrap_or_default();
if tops.len() != values.len() - 1 || tops.iter().any(Option::is_none) {
return Err(format!(
"prompt top-k logprobs incomplete: {} of {} positions have alternatives",
tops.iter().filter(|top| top.is_some()).count(),
values.len() - 1
));
}
let (top_values, top_ids): (Vec<_>, Vec<_>) = tops.into_iter().flatten().unzip();
Some((
std::iter::once(None)
.chain(top_values.into_iter().map(Some))
.collect(),
std::iter::once(None)
.chain(top_ids.into_iter().map(Some))
.collect(),
))
} else {
None
};
self.input_logprobs = Some(InputLogprobs { values, ids, top });
Ok(())
}
pub fn finished(&self) -> bool {
self.finished_reason.is_some()
}
pub fn prefill_finished(&self) -> bool {
self.computed_length >= self.prompt_len
}
pub fn stop_criteria(&self) -> StopCriteria {
StopCriteria {
stop_token_ids: self.stop_token_ids.iter().copied().collect(),
token_limit: self.prompt_len + self.max_new_tokens,
}
}
pub fn abort(&mut self, message: &str, notify_client: bool, status_code: Option<u16>) {
self.to_abort = true;
self.abort_notify_client = notify_client;
self.finished_reason = Some(FinishReason::Abort {
message: message.to_owned(),
err_type: AbortCode::UnknownError,
status_code,
});
}
pub fn check_finished(&mut self, skip_grammar_termination: bool) {
if self.finished() {
return;
}
if !skip_grammar_termination {
if let (Some(grammar), Some(&last)) = (&self.grammar, self.output_ids.last()) {
if grammar
.matcher
.lock()
.expect("matcher lock")
.is_terminated()
{
self.finished_reason = Some(FinishReason::MatchedToken(last));
return;
}
}
}
if self.output_ids.len() as i64 >= self.max_new_tokens {
self.finished_reason = Some(FinishReason::Length(self.max_new_tokens));
return;
}
if let Some(&last) = self.output_ids.last() {
if self.stop_token_ids.contains(&last) {
self.finished_reason = Some(FinishReason::MatchedToken(last));
}
}
}
pub fn output_due(&self, default_stream_interval: i64) -> bool {
if self.finished() {
return !self.finished_output;
}
let generated = self.output_ids.len() as i64;
if self.stream {
let interval = self
.stream_interval
.unwrap_or(default_stream_interval)
.max(1);
if interval > 1 {
generated % interval == 1
} else {
generated % interval == 0
}
} else {
generated == 1 || generated % FORCED_STREAM_INTERVAL == 0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn state(max_new_tokens: i64, stop: &[i32]) -> RequestState {
RequestState::new(
vec![1, 2, 3],
max_new_tokens,
stop.iter().copied().collect(),
true,
None,
false,
)
}
#[test]
fn the_budget_ends_a_request_before_a_stop_token() {
let mut request = state(2, &[7]);
request.output_ids = vec![1, 7];
request.check_finished(false);
assert_eq!(request.finished_reason, Some(FinishReason::Length(2)));
}
#[test]
fn a_stop_token_ends_a_request() {
let mut request = state(10, &[7]);
request.output_ids = vec![1];
request.check_finished(false);
assert!(!request.finished());
request.output_ids.push(7);
request.check_finished(false);
assert_eq!(request.finished_reason, Some(FinishReason::MatchedToken(7)));
}
#[test]
fn an_abort_is_final() {
let mut request = state(10, &[]);
request.abort("AbortReq from client", false, None);
request.output_ids = vec![1; 20];
request.check_finished(false);
assert_eq!(
request.finished_reason,
Some(FinishReason::abort("AbortReq from client"))
);
assert_eq!(request.finished_reason.unwrap().wire_type(), "abort");
}
#[test]
fn stream_intervals_follow_the_python_schedule() {
let due = |stream: bool, interval: Option<i64>, generated: usize| {
let mut request = RequestState::new(
vec![1, 2, 3],
1000,
BTreeSet::new(),
stream,
interval,
false,
);
request.output_ids = vec![0; generated];
request.output_due(1)
};
assert!((1..5).all(|n| due(true, None, n)));
assert_eq!(
(1..8).map(|n| due(true, Some(3), n)).collect::<Vec<_>>(),
[true, false, false, true, false, false, true]
);
assert!(due(false, None, 1) && !due(false, None, 2) && due(false, None, 50));
}
}