Skip to main content

token_sampling/
candidates.rs

1use core::cmp::Ordering;
2use core::fmt;
3
4/// One token candidate with its current logit and probability.
5#[derive(Clone, Copy, Debug, PartialEq)]
6pub struct Candidate {
7    /// Token identifier from the original logits index.
8    pub id: u32,
9    /// Current logit.
10    pub logit: f32,
11    /// Probability from the most recent softmax.
12    pub p: f32,
13}
14
15/// Errors returned while loading or sampling logits.
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub enum Error {
18    /// The logits slice is empty.
19    EmptyLogits,
20    /// A logit is NaN or positive infinity.
21    InvalidLogit {
22        /// Index of the first invalid value.
23        index: usize,
24    },
25    /// Every input logit is negative infinity.
26    AllMasked,
27    /// The chain finished without a terminal stage selecting a token.
28    NoSelection,
29}
30
31impl fmt::Display for Error {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        match self {
34            Self::EmptyLogits => f.write_str("logits are empty"),
35            Self::InvalidLogit { index } => write!(f, "logit at index {index} is invalid"),
36            Self::AllMasked => f.write_str("all logits are masked"),
37            Self::NoSelection => f.write_str("no sampling stage selected a token"),
38        }
39    }
40}
41
42impl std::error::Error for Error {}
43
44/// Reusable token candidates and stage scratch storage.
45#[derive(Clone, Debug)]
46pub struct Candidates {
47    pub(crate) buf: Vec<Candidate>,
48    pub(crate) counts: Vec<(u32, u32)>,
49    pub(crate) repeat_count: Vec<usize>,
50    pub(crate) max_repeats: Vec<(u32, usize)>,
51    pub(crate) breaker_indices: Vec<usize>,
52    pub(crate) sorted: bool,
53    pub(crate) selected: Option<usize>,
54}
55
56impl Candidates {
57    /// Creates an empty candidate set.
58    pub const fn new() -> Self {
59        Self {
60            buf: Vec::new(),
61            counts: Vec::new(),
62            repeat_count: Vec::new(),
63            max_repeats: Vec::new(),
64            breaker_indices: Vec::new(),
65            sorted: false,
66            selected: None,
67        }
68    }
69
70    /// Creates an empty candidate set sized for `n_vocab` logits.
71    pub fn with_capacity(n_vocab: usize) -> Self {
72        Self {
73            buf: Vec::with_capacity(n_vocab),
74            counts: Vec::new(),
75            repeat_count: Vec::new(),
76            max_repeats: Vec::new(),
77            breaker_indices: Vec::new(),
78            sorted: false,
79            selected: None,
80        }
81    }
82
83    /// Validates and loads logits while retaining allocated storage.
84    pub fn load(&mut self, logits: &[f32]) -> Result<(), Error> {
85        if logits.is_empty() {
86            return Err(Error::EmptyLogits);
87        }
88        let mut any_unmasked = false;
89        for (index, &logit) in logits.iter().enumerate() {
90            if logit.is_nan() || logit == f32::INFINITY {
91                return Err(Error::InvalidLogit { index });
92            }
93            any_unmasked |= logit != f32::NEG_INFINITY;
94        }
95        if !any_unmasked {
96            return Err(Error::AllMasked);
97        }
98
99        reserve_to(&mut self.buf, logits.len());
100        self.buf.clear();
101        self.buf
102            .extend(logits.iter().enumerate().map(|(id, &logit)| Candidate {
103                id: id as u32,
104                logit,
105                p: 0.0,
106            }));
107        self.counts.clear();
108        self.repeat_count.clear();
109        self.max_repeats.clear();
110        self.breaker_indices.clear();
111        self.sorted = false;
112        self.selected = None;
113        Ok(())
114    }
115
116    /// Returns the current candidates in stage order.
117    pub fn as_slice(&self) -> &[Candidate] {
118        &self.buf
119    }
120
121    /// Returns the selected token from the last terminal stage.
122    pub fn selected(&self) -> Option<u32> {
123        self.selected
124            .and_then(|position| self.buf.get(position))
125            .map(|candidate| candidate.id)
126    }
127
128    pub(crate) fn sort_desc(&mut self) {
129        if !self.sorted {
130            self.buf.sort_unstable_by(candidate_order);
131            self.sorted = true;
132        }
133    }
134
135    pub(crate) fn softmax(&mut self, sort: bool) -> Result<(), Error> {
136        if self.buf.is_empty() {
137            return Err(Error::AllMasked);
138        }
139        if !self.sanitize_logits() {
140            return Err(Error::AllMasked);
141        }
142        if sort {
143            self.sort_desc();
144        }
145        let max_logit = self
146            .buf
147            .iter()
148            .map(|candidate| candidate.logit)
149            .fold(f32::NEG_INFINITY, f32::max);
150        let sum = self
151            .buf
152            .iter()
153            .map(|candidate| ((candidate.logit - max_logit) as f64).exp())
154            .sum::<f64>();
155        if !sum.is_finite() || sum <= 0.0 {
156            return Err(Error::AllMasked);
157        }
158        for candidate in &mut self.buf {
159            candidate.p = (((candidate.logit - max_logit) as f64).exp() / sum) as f32;
160        }
161        Ok(())
162    }
163
164    pub(crate) fn sanitize_logits(&mut self) -> bool {
165        let mut any_unmasked = false;
166        let mut changed = false;
167        for candidate in &mut self.buf {
168            if candidate.logit.is_nan() {
169                candidate.logit = f32::NEG_INFINITY;
170                changed = true;
171            } else if candidate.logit == f32::INFINITY {
172                candidate.logit = f32::MAX;
173                changed = true;
174            }
175            any_unmasked |= candidate.logit.is_finite();
176        }
177        if changed {
178            self.sorted = false;
179        }
180        any_unmasked
181    }
182}
183
184impl Default for Candidates {
185    fn default() -> Self {
186        Self::new()
187    }
188}
189
190pub(crate) fn candidate_order(left: &Candidate, right: &Candidate) -> Ordering {
191    right
192        .logit
193        .total_cmp(&left.logit)
194        .then_with(|| left.id.cmp(&right.id))
195}
196
197fn reserve_to<T>(values: &mut Vec<T>, capacity: usize) {
198    if values.capacity() < capacity {
199        values.reserve_exact(capacity - values.capacity());
200    }
201}