use std::{
collections::{HashMap, VecDeque},
time::{Duration, Instant},
};
use ts_scheduler::{ForwardBatchView, ForwardEvent, ForwardEventKind, SchedulerAbortView};
use ts_wire::output::FinishDetails;
use crate::{
codec::{Reader, Writer},
request::{AbortCode, FinishReason, InputLogprobs, RequestState},
request_stats::{self, wall_seconds, RequestTimings},
};
const NUMERICAL_ERROR: &str = "Request terminated: numerical corruption (NaN logits or \
out-of-vocab sample) detected during generation.";
const PENDING_ABORT_TTL: Duration = Duration::from_secs(300);
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ForwardResult {
pub output_tokens: Vec<i32>,
pub output_lengths: Vec<i32>,
pub output_logprobs: Option<Vec<f64>>,
pub nan_flags: Option<Vec<i32>>,
pub next_input_ids: Option<Vec<Vec<i32>>>,
pub serving_compiles: Option<(u64, f64)>,
pub grammar_terminated_at: Option<Vec<i32>>,
pub input_token_logprobs: Option<Vec<f64>>,
pub input_top_logprobs: Option<TopLogprobs>,
pub output_top_logprobs: Option<TopLogprobs>,
}
pub use ts_wire::output::TopLogprobs;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InputLogprobPlan {
pub row_starts: Vec<i64>,
pub counts: Vec<i64>,
pub position_starts: Vec<i64>,
pub top_k: usize,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct LogprobPlan {
pub input: Option<InputLogprobPlan>,
pub sampled_top_k: usize,
}
impl ForwardResult {
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Writer::new();
out.i32s(&self.output_tokens);
out.i32s(&self.output_lengths);
out.option(self.output_logprobs.as_deref(), Writer::f64s);
out.option(self.nan_flags.as_deref(), Writer::i32s);
out.option(self.next_input_ids.as_deref(), |out, rows| {
out.count(rows.len());
for row in rows {
out.i32s(row);
}
});
out.option(self.grammar_terminated_at.as_deref(), Writer::i32s);
out.option(self.input_token_logprobs.as_deref(), Writer::f64s);
out.finish()
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
let mut reader = Reader::new(bytes);
let output_tokens = reader.i32s()?;
let output_lengths = reader.i32s()?;
let output_logprobs = reader.option(Reader::f64s)?;
let nan_flags = reader.option(Reader::i32s)?;
let next_input_ids = reader.option(|reader| {
let rows = reader.count()?;
(0..rows).map(|_| reader.i32s()).collect()
})?;
let grammar_terminated_at = reader.option(Reader::i32s)?;
let input_token_logprobs = reader.option(Reader::f64s)?;
reader.finish(Self {
output_tokens,
output_lengths,
output_logprobs,
nan_flags,
next_input_ids,
serving_compiles: None,
grammar_terminated_at,
input_token_logprobs,
input_top_logprobs: None,
output_top_logprobs: None,
})
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct NgramWindows {
pub tokens: Vec<Vec<i32>>,
pub positions: Vec<i64>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct HistorySeeds {
pub slots: Vec<i32>,
pub prefix_lengths: Vec<i64>,
pub tokens: Vec<Vec<i32>>,
}
#[derive(Debug, Default)]
pub struct HistoryRows {
owners: HashMap<i32, u64>,
}
impl HistoryRows {
pub fn seeds(
&mut self,
batch: &ForwardBatchView,
outputs: &OutputProcessor,
) -> Result<Option<HistorySeeds>, CommitError> {
let mut seeds = HistorySeeds::default();
for (i, rid) in batch.request_ids.iter().enumerate() {
let slot = batch.request_pool_indices[i];
let state = outputs
.state(rid)
.ok_or_else(|| CommitError::History(rid.clone()))?;
let held = self.owners.insert(slot, state.admission) == Some(state.admission);
let total = state.prompt_ids.len() + state.output_ids.len();
let boundary = if i < batch.num_extends {
batch.extend_prefix_lens[i].max(0) as usize
} else {
total.saturating_sub(1)
};
if boundary == 0 || held {
continue;
}
if boundary > total {
return Err(CommitError::History(rid.clone()));
}
let prompt = &state.prompt_ids;
let prefix: Vec<i32> = if boundary <= prompt.len() {
prompt[..boundary].to_vec()
} else {
prompt
.iter()
.chain(&state.output_ids[..boundary - prompt.len()])
.copied()
.collect()
};
seeds.slots.push(slot);
seeds.prefix_lengths.push(boundary as i64);
seeds.tokens.push(prefix);
}
Ok((!seeds.slots.is_empty()).then_some(seeds))
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct StreamedOutput {
pub rid: String,
pub output_ids: Vec<i32>,
pub finish_reason: Option<&'static str>,
pub prompt_tokens: i64,
pub completion_tokens: i64,
pub cached_tokens: i64,
pub output_logprobs: Vec<f64>,
pub finish_details: Option<FinishDetails>,
pub spec: Option<(i64, Option<f64>)>,
pub input_logprobs: Option<InputLogprobs>,
pub output_top_logprobs: Option<TopLogprobs>,
}
#[derive(Debug, Default)]
pub struct Committed {
pub events: Vec<ForwardEvent>,
pub outputs: Vec<StreamedOutput>,
pub released: Vec<String>,
pub stats: CommitStats,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct CommitStats {
pub nan_aborts: i64,
pub capacity_aborts: i64,
pub spec_decode_slots: i64,
pub spec_accepted_tokens: i64,
}
impl CommitStats {
pub fn add(&mut self, other: CommitStats) {
self.nan_aborts += other.nan_aborts;
self.capacity_aborts += other.capacity_aborts;
self.spec_decode_slots += other.spec_decode_slots;
self.spec_accepted_tokens += other.spec_accepted_tokens;
}
}
#[derive(Debug, thiserror::Error)]
pub enum CommitError {
#[error("request {rid}: the grammar rejects sampled token {token}")]
GrammarRejected {
rid: String,
token: i32,
},
#[error("forward result for {rows} rows has {found} {column}")]
Shape {
rows: usize,
column: &'static str,
found: usize,
},
#[error("decode request {0} kept no token")]
EmptyDecode(String),
#[error("request {rid} sampled {sampled}, but its first speculative candidate is {candidate}")]
BootstrapMismatch {
rid: String,
sampled: i32,
candidate: i32,
},
#[error("the n-gram window of {0} reaches past its tokens")]
NgramWindow(String),
#[error("the token-history prefix of {0} reaches past its tokens")]
History(String),
#[error("request {rid}: {detail}")]
InputLogprobs {
rid: String,
detail: String,
},
#[error(
"request {rid} committed {total} tokens, past the physical KV extent of {limit}; \
the next KV write would run out of bounds"
)]
PhysicalExtent {
rid: String,
total: i64,
limit: i64,
},
}
fn window<T: Clone>(values: &[T], start: usize, len: usize) -> Vec<T> {
window_ref(values, start, len).to_vec()
}
fn window_ref<T>(values: &[T], start: usize, len: usize) -> &[T] {
let end = (start + len).min(values.len());
&values[start.min(end)..end]
}
fn event(kind: ForwardEventKind, rid: &str, tokens: Vec<i32>, reserve: i32) -> ForwardEvent {
ForwardEvent {
kind,
request_id: rid.to_owned(),
tokens,
spec_candidate_ids: Vec::new(),
reserve_num_tokens: reserve,
bootstrap_token: -1,
}
}
fn extend_result(rid: &str, tokens: Vec<i32>) -> ForwardEvent {
event(ForwardEventKind::ExtendResult, rid, tokens, 0)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Role {
Plain,
Prefill,
Decode,
}
fn finish(rid: &str, nan_detected: bool) -> ForwardEvent {
let kind = if nan_detected {
ForwardEventKind::Abort
} else {
ForwardEventKind::Finish
};
event(kind, rid, Vec::new(), 0)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FinishLog {
pub enabled: bool,
pub request_stats: bool,
}
pub struct OutputProcessor {
states: HashMap<String, RequestState>,
pending_aborts: VecDeque<(String, Instant)>,
spec_num_tokens: Option<usize>,
stream_interval: i64,
physical_context_len: Option<i64>,
role: Role,
finish_log: FinishLog,
last_commit_at: f64,
admissions: u64,
}
impl OutputProcessor {
pub fn new(
spec_num_tokens: Option<usize>,
stream_interval: i64,
physical_context_len: Option<i64>,
role: Role,
finish_log: FinishLog,
) -> Self {
Self {
states: HashMap::new(),
pending_aborts: VecDeque::new(),
spec_num_tokens,
stream_interval,
physical_context_len,
role,
finish_log,
last_commit_at: 0.0,
admissions: 0,
}
}
pub fn attach_grammar(&mut self, rid: &str, grammar: crate::request::RequestGrammar) {
if let Some(state) = self.states.get_mut(rid) {
state.grammar = Some(grammar);
}
}
pub fn mark_scheduled(&mut self, rids: &[String]) {
if !self.finish_log.request_stats {
return;
}
let now = wall_seconds();
for rid in rids {
if let Some(timings) = self.states.get_mut(rid).and_then(|s| s.timings.as_mut()) {
timings.mark_scheduled(now);
}
}
}
fn log_end(&self, rid: &str, state: &RequestState) {
if !self.finish_log.enabled {
return;
}
if let Some(timings) = &state.timings {
tracing::info!(
"{}",
request_stats::stats_line(
rid,
state,
timings,
wall_seconds(),
self.spec_num_tokens
)
);
}
}
pub fn role(&self) -> Role {
self.role
}
pub fn spec_num_tokens(&self) -> Option<usize> {
self.spec_num_tokens
}
pub fn take_submitted_at(&mut self, rid: &str) -> Option<Instant> {
self.states.get_mut(rid)?.submitted_at.take()
}
pub fn mark_submitted(&mut self, rid: &str, at: Instant) {
if let Some(state) = self.states.get_mut(rid) {
state.submitted_at = Some(at);
}
}
pub fn num_running(&self) -> usize {
self.states.len()
}
pub fn state(&self, rid: &str) -> Option<&RequestState> {
self.states.get(rid)
}
pub fn register(&mut self, rid: String, mut state: RequestState) {
self.admissions += 1;
state.admission = self.admissions;
if let Some(index) = self.pending_aborts.iter().position(|(id, _)| *id == rid) {
self.pending_aborts.remove(index);
state.abort("AbortReq from client", false, None);
}
if self.role == Role::Decode {
state.computed_length = state.prompt_len;
}
if self.finish_log.request_stats {
state.timings = Some(RequestTimings::default());
}
self.states.insert(rid, state);
}
pub fn finish_prefill(&mut self, rid: &str) -> Committed {
let mut committed = Committed::default();
let Some(mut state) = self.states.remove(rid) else {
return committed;
};
if !state.finished() {
state.finished_reason = Some(FinishReason::Length(state.output_ids.len() as i64));
}
state.finished_output = false;
if !state.to_abort || state.abort_notify_client {
committed.outputs = self.stream(&mut [(rid.to_owned(), &mut state)]);
}
self.log_end(rid, &state);
committed.released.push(rid.to_owned());
committed
}
pub fn remote_prefill_done(
&mut self,
rid: &str,
bootstrap_token: i32,
cached_tokens: i64,
bootstrap_logprob: Option<f64>,
) {
let Some(state) = self.states.get_mut(rid) else {
return;
};
state.cached_tokens = cached_tokens;
state.output_ids.push(bootstrap_token);
if state.return_logprob {
match bootstrap_logprob {
Some(logprob) => state.output_logprobs.push(logprob),
None => tracing::warn!(
rid,
"the request returns logprobs but the prefill node sent none for its first token"
),
}
}
if let Some(grammar) = &state.grammar {
grammar
.matcher
.lock()
.expect("matcher lock")
.accept_token(bootstrap_token);
}
state.check_finished(false);
}
pub fn finish_remote_prefill_only(&mut self, rid: &str) -> Committed {
let mut committed = Committed::default();
let Some(state) = self.states.get(rid) else {
return committed;
};
if !state.finished() {
return committed;
}
let mut state = self.states.remove(rid).expect("present above");
if !state.to_abort || state.abort_notify_client {
committed.outputs = self.stream(&mut [(rid.to_owned(), &mut state)]);
}
let kind = if state.to_abort {
ForwardEventKind::Abort
} else {
ForwardEventKind::Finish
};
committed.events.push(event(kind, rid, Vec::new(), 0));
if let Some(timings) = state.timings.as_mut() {
let now = wall_seconds();
timings.mark_prefill_done(now);
if !state.output_ids.is_empty() {
timings.mark_first_token(now);
}
}
self.log_end(rid, &state);
committed.released.push(rid.to_owned());
committed
}
pub fn fail_transfer(&mut self, rid: &str) -> Committed {
let mut committed = Committed::default();
let Some(mut state) = self.states.remove(rid) else {
return committed;
};
if !state.finished() {
state.abort("PD/EPD remote transfer failed or timed out", false, None);
state.finished_output = false;
committed.outputs = self.stream(&mut [(rid.to_owned(), &mut state)]);
} else if state.abort_notify_client {
state.finished_output = false;
committed.outputs = self.stream(&mut [(rid.to_owned(), &mut state)]);
}
committed.released.push(rid.to_owned());
committed
}
pub fn mark_abort(&mut self, rid: &str, notify_client: bool) {
if let Some(state) = self.states.get_mut(rid) {
let message = if notify_client {
"Aborted by pause"
} else {
"AbortReq from client"
};
state.abort(message, notify_client, None);
return;
}
self.sweep_pending_aborts();
self.pending_aborts
.push_back((rid.to_owned(), Instant::now()));
}
pub fn sweep_pending_aborts(&mut self) {
while let Some((_, at)) = self.pending_aborts.front() {
if at.elapsed() < PENDING_ABORT_TTL {
break;
}
self.pending_aborts.pop_front();
}
}
pub fn publish_finished_at_admission(
&mut self,
rid: &str,
mut state: RequestState,
) -> Vec<StreamedOutput> {
if let Some(index) = self.pending_aborts.iter().position(|(id, _)| id == rid) {
self.pending_aborts.remove(index);
}
state.finished_output = false;
self.stream(&mut [(rid.to_owned(), &mut state)])
}
pub fn rids(&self) -> Vec<String> {
self.states.keys().cloned().collect()
}
pub fn reap_unscheduled(&mut self, rid: &str) -> Committed {
let mut committed = Committed::default();
let Some(mut state) = self.states.remove(rid) else {
return committed;
};
if state.abort_notify_client {
state.finished_output = false;
committed.outputs = self.stream(&mut [(rid.to_owned(), &mut state)]);
}
committed.released.push(rid.to_owned());
committed
}
pub fn fail_unscheduled(
&mut self,
rid: &str,
reason: &str,
status_code: Option<u16>,
) -> Committed {
let mut committed = Committed::default();
let Some(mut state) = self.states.remove(rid) else {
return committed;
};
if !state.finished() {
state.abort(reason, true, status_code);
}
if !state.to_abort || state.abort_notify_client {
state.finished_output = false;
committed.outputs = self.stream(&mut [(rid.to_owned(), &mut state)]);
}
self.log_end(rid, &state);
committed.released.push(rid.to_owned());
committed
}
pub fn finish_scheduler_aborts(&mut self, aborts: &[SchedulerAbortView]) -> Committed {
let mut committed = Committed::default();
for abort in aborts {
let Some(mut state) = self.states.remove(&abort.request_id) else {
continue;
};
if !state.finished() {
state.finished_reason = Some(FinishReason::Abort {
message: format!("Request aborted by the scheduler: {}", abort.detail),
err_type: AbortCode::CapacityAbort,
status_code: None,
});
committed.stats.capacity_aborts += 1;
committed
.outputs
.extend(self.stream(&mut [(abort.request_id.clone(), &mut state)]));
} else if state.abort_notify_client {
committed
.outputs
.extend(self.stream(&mut [(abort.request_id.clone(), &mut state)]));
}
self.log_end(&abort.request_id, &state);
committed.released.push(abort.request_id.clone());
}
committed
}
pub fn logprob_plan(&self, batch: &ForwardBatchView) -> LogprobPlan {
let mut plan = LogprobPlan::default();
let mut input = InputLogprobPlan {
row_starts: Vec::with_capacity(batch.num_extends),
counts: Vec::with_capacity(batch.num_extends),
position_starts: Vec::with_capacity(batch.num_extends),
top_k: 0,
};
let mut row_offset = 0i64;
for i in 0..batch.num_extends {
let state = self.states.get(&batch.request_ids[i]);
let chunk_start = i64::from(batch.extend_prefix_lens[i]);
let chunk_len = i64::from(batch.input_lengths[i]);
let mut row = (0, 0, 0);
if let Some(state) = state {
if state.return_logprob {
plan.sampled_top_k = plan.sampled_top_k.max(state.top_logprobs_num);
}
let lo = chunk_start.max(state.logprob_start_len);
let hi = (chunk_start + chunk_len).min(state.prompt_len - 1);
if state.wants_input_logprobs() && state.input_logprobs.is_none() && lo < hi {
row = (row_offset + lo - chunk_start, hi - lo, lo);
input.top_k = input.top_k.max(state.top_logprobs_num);
}
}
input.row_starts.push(row.0);
input.counts.push(row.1);
input.position_starts.push(row.2);
row_offset += chunk_len;
}
if input.counts.iter().any(|&count| count > 0) {
plan.input = Some(input);
}
plan
}
fn record_input_logprobs(
&mut self,
batch: &ForwardBatchView,
plan: &InputLogprobPlan,
result: &ForwardResult,
) -> Result<(), CommitError> {
let Some(values) = &result.input_token_logprobs else {
return Ok(());
};
let rows: i64 = plan.counts.iter().sum();
if values.len() as i64 != rows {
return Err(CommitError::Shape {
rows: rows as usize,
column: "input_token_logprobs",
found: values.len(),
});
}
let mut cursor = 0usize;
for (i, &count) in plan.counts.iter().enumerate() {
let count = count as usize;
if count == 0 {
continue;
}
let rid = &batch.request_ids[i];
if let Some(state) = self.states.get_mut(rid) {
if state.input_logprob_rows.is_some() {
let top = result.input_top_logprobs.as_ref().map(|(values, ids)| {
(
window_ref(values, cursor, count),
window_ref(ids, cursor, count),
)
});
state
.record_input_logprobs(
plan.position_starts[i],
&values[cursor..cursor + count],
top,
)
.map_err(|detail| CommitError::InputLogprobs {
rid: rid.clone(),
detail,
})?;
}
}
cursor += count;
}
Ok(())
}
pub fn ngram_windows(
&self,
batch: &ForwardBatchView,
context_len: usize,
) -> Result<NgramWindows, CommitError> {
let mut windows = NgramWindows::default();
for (i, rid) in batch.request_ids.iter().enumerate() {
let state = self
.states
.get(rid)
.ok_or_else(|| CommitError::NgramWindow(rid.clone()))?;
let prompt_len = state.prompt_ids.len() as i64;
let total = prompt_len + state.output_ids.len() as i64;
let start = if i < batch.num_extends {
let start = i64::from(batch.extend_prefix_lens[i]);
if start < 0 || start + i64::from(batch.input_lengths[i]) > total {
return Err(CommitError::NgramWindow(rid.clone()));
}
start
} else {
if total == 0 {
return Err(CommitError::NgramWindow(rid.clone()));
}
total - 1
};
let token = |p: i64| -> i32 {
if p < 0 || p >= total {
-1
} else if p < prompt_len {
state.prompt_ids[p as usize]
} else {
state.output_ids[(p - prompt_len) as usize]
}
};
windows.tokens.push(
(0..=context_len as i64)
.map(|back| token(start - back))
.collect(),
);
windows.positions.push(start);
}
Ok(windows)
}
pub fn commit(
&mut self,
batch: &ForwardBatchView,
input_logprobs: Option<&InputLogprobPlan>,
result: &ForwardResult,
) -> Result<Committed, CommitError> {
let rows = batch.request_ids.len();
let check = |column, found| {
if found == rows {
Ok(())
} else {
Err(CommitError::Shape {
rows,
column,
found,
})
}
};
check("output_lengths", result.output_lengths.len())?;
if let Some(flags) = &result.nan_flags {
check("nan_flags", flags.len())?;
}
let (stats_now, step, prefilling_others) = if self.finish_log.request_stats {
let now = wall_seconds();
let step = if self.last_commit_at > 0.0 {
now - self.last_commit_at
} else {
0.0
};
self.last_commit_at = now;
(now, step, batch.num_extends > 0)
} else {
(0.0, 0.0, false)
};
if let Some(plan) = input_logprobs {
self.record_input_logprobs(batch, plan, result)?;
}
for i in 0..batch.num_extends {
if let Some(state) = self.states.get_mut(&batch.request_ids[i]) {
let hit = i64::from(batch.extend_prefix_lens[i] + batch.extend_replay_lens[i]);
state.cached_tokens += (hit - state.computed_length).max(0);
}
}
let mut committed = Committed::default();
let mut streamed: Vec<String> = Vec::new();
let mut pt = 0usize;
for i in 0..rows {
let rid = &batch.request_ids[i];
let output_length = result.output_lengths[i].max(0) as usize;
let mut model_ids = window(&result.output_tokens, pt, output_length);
let mut model_logprobs = result
.output_logprobs
.as_ref()
.map(|lps| window(lps, pt, output_length));
let is_decode = i >= batch.num_extends;
let model_top = match &result.output_top_logprobs {
Some((values, ids)) if !is_decode => Some((
window_ref(values, pt, output_length),
window_ref(ids, pt, output_length),
)),
_ => None,
};
pt += if is_decode {
self.spec_num_tokens.unwrap_or(1)
} else {
output_length
};
let mid_chunk = !is_decode
&& batch.extend_prefix_lens[i] + batch.input_lengths[i] < batch.prefill_lengths[i];
let nan_flag = result.nan_flags.as_ref().is_some_and(|flags| flags[i] != 0);
let Some(state) = self.states.get_mut(rid) else {
committed.events.push(extend_result(
rid,
if mid_chunk { Vec::new() } else { model_ids },
));
continue;
};
if i < batch.num_extends {
state.computed_length =
i64::from(batch.input_lengths[i] + batch.extend_prefix_lens[i]);
} else {
state.computed_length += i64::from(batch.input_lengths[i]);
}
if mid_chunk {
if nan_flag {
state.numerical_error_detected = true;
}
committed.events.push(extend_result(rid, Vec::new()));
continue;
}
if !state.prefill_finished() {
continue;
}
if is_decode && output_length == 0 {
return Err(CommitError::EmptyDecode(rid.clone()));
}
if !is_decode {
state
.finalize_input_logprobs()
.map_err(|detail| CommitError::InputLogprobs {
rid: rid.clone(),
detail,
})?;
}
if let Some(timings) = state.timings.as_mut() {
timings.mark_prefill_done(stats_now);
if is_decode {
timings.record_decode_step(step, prefilling_others);
}
}
if is_decode && self.spec_num_tokens.is_some() {
committed.stats.spec_decode_slots += 1;
committed.stats.spec_accepted_tokens += output_length as i64 - 1;
}
let nan_detected = nan_flag || state.numerical_error_detected;
if nan_detected && !state.finished() {
committed.stats.nan_aborts += 1;
state.finished_reason = Some(FinishReason::Abort {
message: NUMERICAL_ERROR.to_owned(),
err_type: AbortCode::NumericalError,
status_code: None,
});
model_ids.truncate(1);
if let Some(lps) = model_logprobs.as_mut() {
lps.truncate(1);
}
tracing::warn!(rid, "request terminated: NaN detected in logits");
}
if is_decode && self.spec_num_tokens.is_some() {
state.spec_verify_ct += 1;
if let Some(limit) = self.physical_context_len {
let total =
state.prompt_len + state.output_ids.len() as i64 + output_length as i64;
if total > limit {
return Err(CommitError::PhysicalExtent {
rid: rid.clone(),
total,
limit,
});
}
}
}
let hostfunc = result.grammar_terminated_at.is_some();
let terminated_at = match (&result.grammar_terminated_at, &state.grammar) {
(Some(indices), Some(_)) => indices.get(i).copied().unwrap_or(-1),
_ => -1,
};
for (j, &token) in model_ids.iter().enumerate() {
state.output_ids.push(token);
if !hostfunc {
if let Some(grammar) = &state.grammar {
let mut matcher = grammar.matcher.lock().expect("matcher lock");
if !matcher.is_terminated() && !matcher.accept_token(token) {
return Err(CommitError::GrammarRejected {
rid: rid.clone(),
token,
});
}
}
}
if state.return_logprob {
if let Some(lp) = model_logprobs.as_ref().and_then(|lps| lps.get(j)) {
state.output_logprobs.push(*lp);
}
}
let finished_before = state.finished();
if let Some((top_values, top_ids)) = state.output_top_logprobs.as_mut() {
match model_top.filter(|(values, _)| j < values.len()) {
Some((values, ids)) => {
let k = state.top_logprobs_num;
top_values.push(values[j][..k.min(values[j].len())].to_vec());
top_ids.push(ids[j][..k.min(ids[j].len())].to_vec());
}
None if !finished_before => {
state.finished_reason = Some(FinishReason::abort(
"top-k logprobs are not computed for this step (decode steps \
return none)",
));
}
None => {}
}
}
if terminated_at == j as i32 {
state.finished_reason = Some(FinishReason::MatchedToken(token));
} else {
state.check_finished(hostfunc);
}
if state.finished() {
state.accept_draft_tokens = Some(if state.spec_verify_ct > 0 {
(state.output_ids.len() as f64 - 1.0) / state.spec_verify_ct as f64
} else {
0.0
});
if self.finish_log.enabled && !self.finish_log.request_stats {
tracing::info!("{}", request_stats::accept_length_line(rid, state));
}
break;
}
}
if !state.output_ids.is_empty() {
if let Some(timings) = state.timings.as_mut() {
timings.mark_first_token(stats_now);
}
}
let mut spec_candidate_ids = Vec::new();
if self.role == Role::Prefill && !model_ids.is_empty() && !nan_detected {
if let Some(candidates) =
result.next_input_ids.as_ref().and_then(|rows| rows.get(i))
{
if let Some(&first) = candidates.first() {
if first != model_ids[0] {
return Err(CommitError::BootstrapMismatch {
rid: rid.clone(),
sampled: model_ids[0],
candidate: first,
});
}
}
spec_candidate_ids = candidates.clone();
}
}
let mut extend = extend_result(rid, model_ids);
extend.spec_candidate_ids = spec_candidate_ids;
committed.events.push(extend);
if self.role == Role::Prefill {
if nan_detected {
committed.events.push(finish(rid, true));
self.release(rid, &mut committed);
}
continue;
}
if state.to_abort && state.finished() {
committed.events.push(finish(rid, nan_detected));
if state.abort_notify_client {
streamed.push(rid.clone());
} else {
self.release(rid, &mut committed);
}
continue;
}
streamed.push(rid.clone());
if state.finished() {
committed.events.push(finish(rid, nan_detected));
} else if is_decode {
committed.events.push(event(
ForwardEventKind::UpdateReserveNumTokens,
rid,
Vec::new(),
output_length as i32,
));
}
}
let mut states: Vec<(String, RequestState)> = streamed
.iter()
.filter_map(|rid| self.states.remove_entry(rid))
.collect();
let mut refs: Vec<(String, &mut RequestState)> = states
.iter_mut()
.map(|(rid, state)| (rid.clone(), state))
.collect();
committed.outputs = self.stream(&mut refs);
for (rid, state) in states {
if state.finished() {
self.log_end(&rid, &state);
committed.released.push(rid);
} else {
self.states.insert(rid, state);
}
}
Ok(committed)
}
fn release(&mut self, rid: &str, committed: &mut Committed) {
if let Some(state) = self.states.remove(rid) {
self.log_end(rid, &state);
committed.released.push(rid.to_owned());
}
}
fn stream(&self, states: &mut [(String, &mut RequestState)]) -> Vec<StreamedOutput> {
let mut outputs = Vec::new();
for (rid, state) in states.iter_mut() {
if !state.output_due(self.stream_interval) {
continue;
}
if state.finished() {
state.finished_output = true;
}
let sent = state.send_token_offset;
let output_ids = state.output_ids[sent..].to_vec();
state.send_token_offset = state.output_ids.len();
let n_new = output_ids.len();
let output_logprobs = if state.return_logprob {
let start = state.output_logprobs.len().saturating_sub(n_new);
state.output_logprobs[start..].to_vec()
} else {
Vec::new()
};
let input_logprobs = if state.input_logprobs_sent {
None
} else {
state.input_logprobs.clone()
};
state.input_logprobs_sent |= input_logprobs.is_some();
let output_top_logprobs = state.output_top_logprobs.as_ref().map(|(values, ids)| {
(
values[values.len().saturating_sub(n_new)..].to_vec(),
ids[ids.len().saturating_sub(n_new)..].to_vec(),
)
});
outputs.push(StreamedOutput {
rid: rid.clone(),
output_ids,
finish_reason: state.finished_reason.as_ref().map(FinishReason::wire_type),
prompt_tokens: state.prompt_len,
completion_tokens: state.output_ids.len() as i64,
cached_tokens: state.cached_tokens,
output_logprobs,
finish_details: state.finished_reason.as_ref().map(FinishReason::details),
spec: self
.spec_num_tokens
.map(|_| (state.spec_verify_ct, state.accept_draft_tokens)),
input_logprobs,
output_top_logprobs,
});
}
outputs
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use super::*;
fn batch(rows: &[(&str, i32, i32, i32)], num_extends: usize) -> ForwardBatchView {
ForwardBatchView {
request_ids: rows.iter().map(|r| r.0.to_owned()).collect(),
request_pool_indices: (1..=rows.len() as i32).collect(),
input_lengths: rows.iter().map(|r| r.1).collect(),
prefill_lengths: rows.iter().map(|r| r.3).collect(),
extend_prefix_lens: rows[..num_extends].iter().map(|r| r.2).collect(),
extend_replay_lens: vec![0; num_extends],
num_extends,
}
}
fn result(tokens: &[i32], lengths: &[i32]) -> ForwardResult {
ForwardResult {
output_tokens: tokens.to_vec(),
output_lengths: lengths.to_vec(),
output_logprobs: None,
nan_flags: None,
next_input_ids: None,
serving_compiles: None,
grammar_terminated_at: None,
input_token_logprobs: None,
input_top_logprobs: None,
output_top_logprobs: None,
}
}
const NO_FINISH_LOG: FinishLog = FinishLog {
enabled: false,
request_stats: false,
};
fn processor(spec_num_tokens: Option<usize>) -> OutputProcessor {
OutputProcessor::new(spec_num_tokens, 1, Some(4096), Role::Plain, NO_FINISH_LOG)
}
fn register(p: &mut OutputProcessor, rid: &str, prompt: i64, max_new: i64, stop: &[i32]) {
let stops: BTreeSet<i32> = stop.iter().copied().collect();
p.register(
rid.to_owned(),
RequestState::new(vec![0; prompt as usize], max_new, stops, true, None, false),
);
}
fn kinds(committed: &Committed) -> Vec<(String, ForwardEventKind, Vec<i32>)> {
committed
.events
.iter()
.map(|e| (e.request_id.clone(), e.kind, e.tokens.clone()))
.collect()
}
fn register_scored(
p: &mut OutputProcessor,
rid: &str,
prompt: Vec<i32>,
start: i64,
top_k: usize,
computes_prompt_logprobs: bool,
) {
let mut state = RequestState::new(prompt, 4, BTreeSet::new(), true, None, true);
state.set_logprobs(crate::request::LogprobRequest {
start,
top_logprobs_num: top_k,
computes_prompt_logprobs,
});
p.register(rid.to_owned(), state);
}
fn scored(plan: &InputLogprobPlan, tokens: &[i32]) -> ForwardResult {
let positions: Vec<i64> = plan
.counts
.iter()
.zip(&plan.position_starts)
.flat_map(|(&count, &start)| start..start + count)
.collect();
let top = |p: &i64| {
(
(0..plan.top_k).map(|r| -(*p as f64) - r as f64).collect(),
(0..plan.top_k).map(|r| 100 + r as i32).collect(),
)
};
ForwardResult {
input_token_logprobs: Some(positions.iter().map(|&p| -(p as f64)).collect()),
input_top_logprobs: (plan.top_k > 0).then(|| positions.iter().map(top).unzip()),
..result(tokens, &vec![1; tokens.len()])
}
}
fn register_history(p: &mut OutputProcessor, rid: &str, prompt: &[i32], output: &[i32]) {
let mut state = RequestState::new(prompt.to_vec(), 8, BTreeSet::new(), true, None, false);
state.output_ids = output.to_vec();
p.register(rid.to_owned(), state);
}
fn history_batch(rows: &[(&str, i32, i32)], num_extends: usize) -> ForwardBatchView {
ForwardBatchView {
request_ids: rows.iter().map(|r| r.0.to_owned()).collect(),
request_pool_indices: rows.iter().map(|r| r.1).collect(),
input_lengths: vec![1; rows.len()],
prefill_lengths: vec![0; rows.len()],
extend_prefix_lens: rows[..num_extends].iter().map(|r| r.2).collect(),
extend_replay_lens: vec![0; num_extends],
num_extends,
}
}
#[test]
fn history_seeds_cover_the_prompt_and_output_prefix() {
let mut p = processor(None);
register_history(&mut p, "a", &[10, 11, 12, 13], &[20, 21]);
register_history(&mut p, "b", &[30, 31], &[]);
let seeds = HistoryRows::default()
.seeds(&history_batch(&[("a", 1, 5), ("b", 2, 2)], 2), &p)
.unwrap();
assert_eq!(
seeds,
Some(HistorySeeds {
slots: vec![1, 2],
prefix_lengths: vec![5, 2],
tokens: vec![vec![10, 11, 12, 13, 20], vec![30, 31]],
})
);
}
#[test]
fn a_fresh_extend_seeds_nothing_and_claims_its_row() {
let mut p = processor(None);
register_history(&mut p, "a", &[10, 11, 12, 13], &[20, 21]);
let mut rows = HistoryRows::default();
assert_eq!(
rows.seeds(&history_batch(&[("a", 1, 0)], 1), &p).unwrap(),
None
);
assert_eq!(
rows.seeds(&history_batch(&[("a", 1, 4)], 1), &p).unwrap(),
None
);
assert_eq!(
rows.seeds(&history_batch(&[("a", 1, 0)], 0), &p).unwrap(),
None
);
}
#[test]
fn a_remote_prefill_landing_seeds_its_first_decode() {
let mut p = processor(None);
register_history(&mut p, "b", &[30, 31, 32], &[40]);
let mut rows = HistoryRows::default();
let landing = history_batch(&[("b", 2, 0)], 0);
assert_eq!(
rows.seeds(&landing, &p).unwrap(),
Some(HistorySeeds {
slots: vec![2],
prefix_lengths: vec![3],
tokens: vec![vec![30, 31, 32]],
})
);
assert_eq!(rows.seeds(&landing, &p).unwrap(), None);
}
#[test]
fn a_slot_handoff_and_a_recovery_reseed() {
let mut p = processor(None);
register_history(&mut p, "a", &[10, 11, 12, 13], &[20, 21]);
register_history(&mut p, "b", &[30, 31], &[]);
let mut rows = HistoryRows::default();
rows.seeds(&history_batch(&[("a", 1, 0)], 1), &p).unwrap();
let handoff = rows.seeds(&history_batch(&[("b", 1, 2)], 1), &p).unwrap();
assert_eq!(handoff.unwrap().slots, [1]);
let recovery = rows.seeds(&history_batch(&[("a", 1, 4)], 1), &p).unwrap();
assert_eq!(recovery.unwrap().prefix_lengths, [4]);
}
#[test]
fn a_reused_request_id_does_not_inherit_the_row() {
let mut p = processor(None);
register_history(&mut p, "a", &[10, 11, 12, 13], &[]);
let mut rows = HistoryRows::default();
rows.seeds(&history_batch(&[("a", 1, 0)], 1), &p).unwrap();
p.states.remove("a");
register_history(&mut p, "a", &[50, 51, 52, 53], &[]);
assert_eq!(
rows.seeds(&history_batch(&[("a", 1, 3)], 1), &p).unwrap(),
Some(HistorySeeds {
slots: vec![1],
prefix_lengths: vec![3],
tokens: vec![vec![50, 51, 52]],
})
);
}
#[test]
fn a_seed_past_the_request_tokens_is_an_engine_error() {
let mut p = processor(None);
register_history(&mut p, "b", &[30, 31], &[]);
let past = history_batch(&[("b", 2, 3)], 1);
assert!(matches!(
HistoryRows::default().seeds(&past, &p),
Err(CommitError::History(_))
));
}
#[test]
fn prompt_logprobs_accumulate_over_chunks_and_ship_once() {
let mut p = processor(None);
register_scored(&mut p, "r", vec![5, 6, 7, 8, 9, 10], 1, 0, true);
let first = batch(&[("r", 3, 0, 6)], 1);
let plan = p.logprob_plan(&first).input.unwrap();
assert_eq!(
(
plan.row_starts.clone(),
plan.counts.clone(),
plan.position_starts.clone()
),
(vec![1], vec![2], vec![1])
);
let c = p.commit(&first, Some(&plan), &scored(&plan, &[0])).unwrap();
assert!(c.outputs.is_empty());
let second = batch(&[("r", 3, 3, 6)], 1);
let plan = p.logprob_plan(&second).input.unwrap();
assert_eq!(
(plan.counts.clone(), plan.position_starts.clone()),
(vec![2], vec![3])
);
let c = p
.commit(&second, Some(&plan), &scored(&plan, &[11]))
.unwrap();
let logprobs = c.outputs[0].input_logprobs.clone().unwrap();
assert_eq!(
logprobs.values,
[None, Some(-1.0), Some(-2.0), Some(-3.0), Some(-4.0)]
);
assert_eq!(logprobs.ids, [6, 7, 8, 9, 10]);
assert!(p.logprob_plan(&batch(&[("r", 1, 0, 6)], 0)).input.is_none());
let c = p
.commit(&batch(&[("r", 1, 0, 6)], 0), None, &result(&[12], &[1]))
.unwrap();
assert!(c.outputs[0].input_logprobs.is_none());
}
#[test]
fn a_re_prefill_overwrites_the_same_prompt_positions() {
let mut p = processor(None);
register_scored(&mut p, "r", vec![5, 6, 7, 8], 0, 0, true);
let chunk = batch(&[("r", 2, 0, 4)], 1);
let plan = p.logprob_plan(&chunk).input.unwrap();
p.commit(&chunk, Some(&plan), &scored(&plan, &[0])).unwrap();
let plan = p.logprob_plan(&chunk).input.unwrap();
assert_eq!(plan.position_starts, [0]);
p.commit(&chunk, Some(&plan), &scored(&plan, &[0])).unwrap();
let last = batch(&[("r", 2, 2, 4)], 1);
let plan = p.logprob_plan(&last).input.unwrap();
let c = p.commit(&last, Some(&plan), &scored(&plan, &[9])).unwrap();
assert_eq!(
c.outputs[0].input_logprobs.as_ref().unwrap().values,
[None, Some(0.0), Some(-1.0), Some(-2.0)]
);
}
#[test]
fn a_gap_in_the_prompt_logprobs_is_an_engine_error() {
let mut p = processor(None);
register_scored(&mut p, "r", vec![5, 6, 7, 8], 0, 0, true);
assert!(matches!(
p.commit(&batch(&[("r", 4, 0, 4)], 1), None, &result(&[9], &[1])),
Err(CommitError::InputLogprobs { .. })
));
}
#[test]
fn top_k_alternatives_ride_the_prompt_rows_and_the_sampled_token() {
let mut p = processor(None);
register_scored(&mut p, "r", vec![5, 6, 7], 0, 2, true);
let prompt = batch(&[("r", 3, 0, 3)], 1);
let plan = p.logprob_plan(&prompt);
assert_eq!(plan.sampled_top_k, 2);
let input = plan.input.unwrap();
let mut forward = scored(&input, &[9]);
forward.output_top_logprobs = Some((vec![vec![-0.5, -1.5, -2.5]], vec![vec![9, 4, 3]]));
let c = p.commit(&prompt, Some(&input), &forward).unwrap();
let output = &c.outputs[0];
let (values, ids) = output.input_logprobs.as_ref().unwrap().top.clone().unwrap();
assert_eq!(
values,
[None, Some(vec![0.0, -1.0]), Some(vec![-1.0, -2.0])]
);
assert_eq!(ids, [None, Some(vec![100, 101]), Some(vec![100, 101])]);
assert_eq!(
output.output_top_logprobs,
Some((vec![vec![-0.5, -1.5]], vec![vec![9, 4]]))
);
let c = p
.commit(&batch(&[("r", 1, 0, 3)], 0), None, &result(&[10], &[1]))
.unwrap();
assert_eq!(
c.outputs[0].finish_reason,
Some("abort"),
"a token without the alternatives its request asked for ends it"
);
}
#[test]
fn the_decode_role_ships_no_prompt_logprobs() {
let mut p = OutputProcessor::new(None, 1, None, Role::Decode, NO_FINISH_LOG);
register_scored(&mut p, "r", vec![5, 6, 7], 0, 0, false);
assert!(!p.state("r").unwrap().wants_input_logprobs());
p.remote_prefill_done("r", 9, 0, Some(-0.25));
let decode = ForwardResult {
output_logprobs: Some(vec![-0.5]),
..result(&[10], &[1])
};
let c = p
.commit(&batch(&[("r", 1, 0, 3)], 0), None, &decode)
.unwrap();
let output = &c.outputs[0];
assert!(output.input_logprobs.is_none());
assert_eq!(output.output_ids, [9, 10]);
assert_eq!(output.output_logprobs, [-0.25, -0.5]);
}
#[test]
fn the_prefill_role_ships_its_prompt_logprobs_with_its_final_output() {
let mut p = OutputProcessor::new(None, 1, None, Role::Prefill, NO_FINISH_LOG);
register_scored(&mut p, "r", vec![5, 6, 7], 0, 0, true);
let prompt = batch(&[("r", 3, 0, 3)], 1);
let plan = p.logprob_plan(&prompt).input.unwrap();
let c = p
.commit(&prompt, Some(&plan), &scored(&plan, &[9]))
.unwrap();
assert!(c.outputs.is_empty());
let c = p.finish_prefill("r");
assert_eq!(
c.outputs[0].input_logprobs.as_ref().unwrap().values,
[None, Some(0.0), Some(-1.0)]
);
}
#[test]
fn ngram_windows_follow_the_committed_tokens() {
let mut p = OutputProcessor::new(None, 1, None, Role::Plain, NO_FINISH_LOG);
p.register(
"r".to_owned(),
RequestState::new(vec![10, 11, 12, 13], 8, BTreeSet::new(), true, None, false),
);
let windows = p.ngram_windows(&batch(&[("r", 2, 2, 4)], 1), 3).unwrap();
assert_eq!(windows.tokens, [vec![12, 11, 10, -1]]);
assert_eq!(windows.positions, [2]);
p.commit(&batch(&[("r", 4, 0, 4)], 1), None, &result(&[20], &[1]))
.unwrap();
let windows = p.ngram_windows(&batch(&[("r", 1, 0, 4)], 0), 3).unwrap();
assert_eq!(windows.tokens, [vec![20, 13, 12, 11]]);
assert_eq!(windows.positions, [4]);
assert!(matches!(
p.ngram_windows(&batch(&[("other", 1, 0, 4)], 0), 3),
Err(CommitError::NgramWindow(_))
));
}
#[test]
fn a_result_survives_the_pipeline_broadcast() {
let full = ForwardResult {
output_tokens: vec![5, -1, 7],
output_lengths: vec![2, 1],
output_logprobs: Some(vec![-0.25, -1.5, f64::NEG_INFINITY]),
nan_flags: Some(vec![0, 1]),
next_input_ids: Some(vec![vec![5, 6], vec![]]),
serving_compiles: None,
grammar_terminated_at: Some(vec![-1, 0]),
input_token_logprobs: Some(vec![-0.5, -2.0]),
..ForwardResult::default()
};
let bare = ForwardResult {
output_tokens: vec![9],
output_lengths: vec![1],
..Default::default()
};
for result in [full, bare] {
let bytes = result.to_bytes();
assert_eq!(ForwardResult::from_bytes(&bytes).as_ref(), Some(&result));
assert_eq!(ForwardResult::from_bytes(&bytes[..bytes.len() - 1]), None);
}
}
#[test]
fn a_request_streams_until_its_stop_token() {
let mut p = processor(None);
register(&mut p, "r", 4, 10, &[2]);
let c = p
.commit(&batch(&[("r", 4, 0, 4)], 1), None, &result(&[7], &[1]))
.unwrap();
assert_eq!(
kinds(&c),
[("r".into(), ForwardEventKind::ExtendResult, vec![7])]
);
assert_eq!(
(c.outputs[0].output_ids.clone(), c.outputs[0].finish_reason),
(vec![7], None)
);
assert_eq!(c.outputs[0].prompt_tokens, 4);
let c = p
.commit(&batch(&[("r", 1, 0, 4)], 0), None, &result(&[8], &[1]))
.unwrap();
assert_eq!(c.events[1].kind, ForwardEventKind::UpdateReserveNumTokens);
assert_eq!(c.events[1].reserve_num_tokens, 1);
let c = p
.commit(&batch(&[("r", 1, 0, 4)], 0), None, &result(&[2], &[1]))
.unwrap();
assert_eq!(
kinds(&c),
[
("r".into(), ForwardEventKind::ExtendResult, vec![2]),
("r".into(), ForwardEventKind::Finish, vec![]),
]
);
assert_eq!(
(c.outputs[0].finish_reason, c.outputs[0].completion_tokens),
(Some("stop"), 3)
);
assert_eq!(c.released, ["r"]);
assert_eq!(p.num_running(), 0);
}
#[test]
fn a_mid_prompt_chunk_reports_its_kv_and_outputs_nothing() {
let mut p = processor(None);
register(&mut p, "r", 10, 10, &[]);
let c = p
.commit(&batch(&[("r", 6, 0, 10)], 1), None, &result(&[99], &[1]))
.unwrap();
assert_eq!(
kinds(&c),
[("r".into(), ForwardEventKind::ExtendResult, vec![])]
);
assert!(c.outputs.is_empty());
assert_eq!(p.state("r").unwrap().computed_length, 6);
let c = p
.commit(&batch(&[("r", 4, 6, 10)], 1), None, &result(&[5], &[1]))
.unwrap();
assert_eq!(c.outputs[0].output_ids, [5]);
}
#[test]
fn the_budget_finishes_with_length() {
let mut p = processor(None);
register(&mut p, "r", 2, 1, &[]);
let c = p
.commit(&batch(&[("r", 2, 0, 2)], 1), None, &result(&[3], &[1]))
.unwrap();
assert_eq!(c.outputs[0].finish_reason, Some("length"));
assert_eq!(c.events[1].kind, ForwardEventKind::Finish);
}
#[test]
fn a_client_abort_finishes_silently_at_the_next_commit() {
let mut p = processor(None);
register(&mut p, "r", 2, 10, &[]);
p.commit(&batch(&[("r", 2, 0, 2)], 1), None, &result(&[3], &[1]))
.unwrap();
p.mark_abort("r", false);
let c = p
.commit(&batch(&[("r", 1, 0, 2)], 0), None, &result(&[4], &[1]))
.unwrap();
assert_eq!(c.events[1].kind, ForwardEventKind::Finish);
assert!(c.outputs.is_empty(), "the client tore down already");
assert_eq!(c.released, ["r"]);
}
#[test]
fn an_abort_before_registration_applies_on_register() {
let mut p = processor(None);
p.mark_abort("early", false);
register(&mut p, "early", 2, 10, &[]);
assert!(p.state("early").unwrap().to_abort);
}
#[test]
fn a_nan_aborts_the_request_and_keeps_one_token() {
let mut p = processor(Some(3));
register(&mut p, "r", 2, 10, &[]);
p.commit(&batch(&[("r", 2, 0, 2)], 1), None, &result(&[3], &[1]))
.unwrap();
let mut nan = result(&[4, 5, 6], &[3]);
nan.nan_flags = Some(vec![1]);
let c = p.commit(&batch(&[("r", 3, 0, 2)], 0), None, &nan).unwrap();
assert_eq!(
kinds(&c),
[
("r".into(), ForwardEventKind::ExtendResult, vec![4]),
("r".into(), ForwardEventKind::Abort, vec![]),
]
);
assert_eq!(c.outputs[0].finish_reason, Some("abort"));
}
#[test]
fn decode_rows_step_over_whole_verify_windows() {
let mut p = processor(Some(3));
for rid in ["a", "b"] {
register(&mut p, rid, 1, 100, &[]);
p.commit(&batch(&[(rid, 1, 0, 1)], 1), None, &result(&[0], &[1]))
.unwrap();
}
let c = p
.commit(
&batch(&[("a", 3, 0, 1), ("b", 3, 0, 1)], 0),
None,
&result(&[10, 11, 0, 20, 21, 22], &[2, 3]),
)
.unwrap();
assert_eq!(c.outputs[0].output_ids, [10, 11]);
assert_eq!(c.outputs[1].output_ids, [20, 21, 22]);
assert_eq!(p.state("a").unwrap().spec_verify_ct, 1);
}
#[test]
fn a_verify_step_past_the_physical_extent_is_an_engine_error() {
let mut p = OutputProcessor::new(Some(4), 1, Some(8), Role::Plain, NO_FINISH_LOG);
register(&mut p, "r", 3, 100, &[]);
p.commit(&batch(&[("r", 3, 0, 3)], 1), None, &result(&[1], &[1]))
.unwrap();
let window = batch(&[("r", 4, 0, 3)], 0);
p.commit(&window, None, &result(&[2, 3, 4, 5], &[4]))
.unwrap();
assert!(matches!(
p.commit(&window, None, &result(&[6, 7, 8, 9], &[4])),
Err(CommitError::PhysicalExtent {
total: 12,
limit: 8,
..
})
));
}
#[test]
fn a_request_that_ended_in_flight_still_reports_its_tokens() {
let mut p = processor(None);
let c = p
.commit(&batch(&[("gone", 1, 0, 1)], 0), None, &result(&[5], &[1]))
.unwrap();
assert_eq!(
kinds(&c),
[("gone".into(), ForwardEventKind::ExtendResult, vec![5])]
);
assert!(c.outputs.is_empty());
}
#[test]
fn a_decode_row_without_a_token_is_an_engine_error() {
let mut p = processor(None);
register(&mut p, "r", 1, 10, &[]);
p.commit(&batch(&[("r", 1, 0, 1)], 1), None, &result(&[1], &[1]))
.unwrap();
let error = p
.commit(&batch(&[("r", 1, 0, 1)], 0), None, &result(&[0], &[0]))
.unwrap_err();
assert!(matches!(error, CommitError::EmptyDecode(_)));
}
#[test]
fn sampled_logprobs_ride_with_their_tokens() {
let mut p = processor(None);
p.register(
"r".to_owned(),
RequestState::new(vec![0], 10, BTreeSet::new(), true, None, true),
);
let mut first = result(&[5], &[1]);
first.output_logprobs = Some(vec![-0.25]);
let c = p
.commit(&batch(&[("r", 1, 0, 1)], 1), None, &first)
.unwrap();
assert_eq!(c.outputs[0].output_logprobs, [-0.25]);
}
}