use crate::scheduler::ActiveSeq;
use crate::scheduler::sample_step::PositionKind;
use metrale_sampling::{SamplingParams, apply_penalties_and_bias};
pub mod adadec_diag;
mod b1_margin;
pub mod f2_confidence;
pub mod forced_think_end;
pub mod forced_token;
pub mod grammar_bitmask;
pub mod mid_word;
pub mod min_tokens_eos;
pub mod pin_tool_call;
pub mod post_close;
pub mod tool_during_think;
#[cfg(test)]
mod pipeline_tests;
#[cfg(test)]
mod steering_off_tests;
#[derive(Debug, Clone)]
pub struct LogitsContext<'a> {
pub scratch: &'a crate::scheduler::sched_ctx::DecodeScratch,
pub tel: &'a dyn crate::scheduler::io::TelemetryIo,
pub clock: &'a dyn crate::scheduler::io::ClockIo,
pub think_end_token: Option<u32>,
pub think_start_token: Option<u32>,
pub tool_call_start_token: Option<u32>,
pub tool_call_end_token: Option<u32>,
pub thinking_closed_by: Option<u32>,
pub verify_pos: usize,
pub boundary_mask: Option<std::sync::Arc<[bool]>>,
pub mid_word_mask: Option<std::sync::Arc<[bool]>>,
pub sampling: SamplingLevers,
pub watchdog: crate::scheduler::helpers::WatchdogParams,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct SamplingLevers {
pub force_temp_zero: bool,
pub fast_greedy_grammar: bool,
pub mtp_verify_sample: bool,
pub fast_masked: bool,
pub fast_greedy_chat: bool,
pub adadec_diagnostic: bool,
pub dflash_masked_verify: bool,
pub disable_watchdogs: bool,
pub forced_token_fastpath: bool,
pub mtp_minp: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcessorOutcome {
Continue,
EmitToken(u32),
}
pub trait LogitsProcessor: Send + Sync {
fn apply(
&self,
logits: &mut [f32],
seq: &mut ActiveSeq,
ctx: &LogitsContext,
) -> ProcessorOutcome;
fn name(&self) -> &'static str;
fn is_argmax_invariant(&self) -> bool {
false
}
}
pub fn run_pipeline(logits: &mut [f32], seq: &mut ActiveSeq, ctx: &LogitsContext) -> Option<u32> {
run_pipeline_with_path(logits, seq, ctx, "verify")
}
pub fn run_pipeline_with_path(
logits: &mut [f32],
seq: &mut ActiveSeq,
ctx: &LogitsContext,
path: &'static str,
) -> Option<u32> {
let stages: [&dyn LogitsProcessor; 9] = [
&min_tokens_eos::EosHoldMask,
&f2_confidence::F2ConfidenceEarlyStop,
&mid_word::MidWordThinkEndMask,
&post_close::PostCloseThinkMask,
&tool_during_think::ToolCallDuringThinkingMask,
&forced_think_end::ForcedThinkEndInjector,
&pin_tool_call::PinToToolCallStart,
&forced_token::ForcedTokenFastPath,
&grammar_bitmask::GrammarBitmaskApply,
];
for stage in stages.iter() {
match stage.apply(logits, seq, ctx) {
ProcessorOutcome::Continue => {}
ProcessorOutcome::EmitToken(tok) => return Some(tok),
}
}
adadec_diag::log_step(ctx.tel.dumps().adadec.as_ref(), logits, seq, path);
None
}
pub fn process_position_logits(
logits: &mut [f32],
seq: &mut ActiveSeq,
ctx: &LogitsContext,
penalties: &SamplingParams,
kind: PositionKind,
) -> Option<u32> {
if ctx.sampling.force_temp_zero {
let mut best_idx: u32 = 0;
let mut best_val: f32 = f32::NEG_INFINITY;
for (j, &v) in logits.iter().enumerate() {
if v > best_val {
best_val = v;
best_idx = j as u32;
}
}
return Some(best_idx);
}
if let Some(forced) = run_pipeline_with_path(logits, seq, ctx, kind.adadec_label()) {
return Some(forced);
}
if kind == PositionKind::FinalDecode {
b1_margin::observe(logits, seq, ctx.tel.stats());
}
let t_pen = ctx.clock.now();
apply_penalties_and_bias(
logits,
penalties,
crate::scheduler::sample_step::penalty_history_scope(
&seq.output_tokens,
ctx.tool_call_end_token,
),
);
if kind == PositionKind::Verify {
ctx.tel
.mark(crate::scheduler::mtp_timing::Phase::Penalties, t_pen);
}
None
}