1use anyhow::{Result, ensure};
4use clap::Args;
5use rand::{Rng, SeedableRng, rngs::StdRng};
6use serde::{Deserialize, Serialize};
7
8#[derive(Args, Clone, Debug, PartialEq, Serialize, Deserialize)]
9#[serde(default, deny_unknown_fields)]
10pub struct Sampling {
11 #[arg(long, default_value_t = 0.0)]
13 pub temperature: f64,
14 #[arg(long, default_value_t = 1.0)]
16 pub top_p: f64,
17 #[arg(long, default_value_t = 0)]
19 pub top_k: usize,
20 #[arg(long)]
22 pub seed: Option<u64>,
23 #[arg(long, default_value_t = 0.0, allow_hyphen_values = true)]
25 pub presence_penalty: f64,
26 #[arg(long, default_value_t = 0.0, allow_hyphen_values = true)]
28 pub frequency_penalty: f64,
29}
30
31impl Default for Sampling {
32 fn default() -> Self {
33 Self {
34 temperature: 0.0,
35 top_p: 1.0,
36 top_k: 0,
37 seed: None,
38 presence_penalty: 0.0,
39 frequency_penalty: 0.0,
40 }
41 }
42}
43
44impl Sampling {
45 pub fn validate(&self) -> Result<()> {
46 ensure!(
47 self.temperature.is_finite() && (0.0..=2.0).contains(&self.temperature),
48 "temperature must be finite and in 0..2"
49 );
50 ensure!(
51 self.top_p.is_finite() && self.top_p > 0.0 && self.top_p <= 1.0,
52 "top_p must be finite and in (0, 1]"
53 );
54 ensure!(self.top_k <= 1_000_000, "top_k must be 0..1000000");
55
56 for (name, value) in [
57 ("presence_penalty", self.presence_penalty),
58 ("frequency_penalty", self.frequency_penalty),
59 ] {
60 ensure!(
61 value.is_finite() && (-2.0..=2.0).contains(&value),
62 "{name} must be finite and in -2..2"
63 );
64 }
65
66 Ok(())
67 }
68
69 pub fn greedy(&self) -> bool {
71 self.temperature == 0.0 && self.presence_penalty == 0.0 && self.frequency_penalty == 0.0
72 }
73
74 pub(crate) fn from_request(value: &serde_json::Value, defaults: &Self) -> Result<Self> {
75 let mut fields = serde_json::to_value(defaults)?;
76
77 for key in [
78 "temperature",
79 "top_p",
80 "top_k",
81 "seed",
82 "presence_penalty",
83 "frequency_penalty",
84 ] {
85 if let Some(v) = value.get(key).filter(|v| !v.is_null()) {
86 fields[key] = v.clone();
87 }
88 }
89
90 let result: Self = serde_json::from_value(fields)?;
91
92 result.validate()?;
93
94 Ok(result)
95 }
96}
97
98pub(crate) struct Sampler {
99 options: Sampling,
100 rng: StdRng,
101 counts: Vec<u32>,
102 candidates: Vec<(u32, f64)>,
103}
104
105impl Sampler {
106 pub(crate) fn set_rng(&mut self, rng: StdRng) {
107 self.rng = rng;
108 }
109
110 pub(crate) fn rng(&self) -> StdRng {
111 self.rng.clone()
112 }
113
114 pub(crate) fn new(options: &Sampling, prompt: &[u32], vocab: usize) -> Result<Self> {
115 options.validate()?;
116
117 let mut sampler = Self {
118 options: options.clone(),
119 rng: options
120 .seed
121 .map(StdRng::seed_from_u64)
122 .unwrap_or_else(StdRng::from_os_rng),
123 counts: vec![0; vocab],
124 candidates: Vec::with_capacity(vocab),
125 };
126
127 for &token in prompt {
128 sampler.accept(token)?;
129 }
130
131 Ok(sampler)
132 }
133
134 pub(crate) fn accept(&mut self, token: u32) -> Result<()> {
135 let count = self
136 .counts
137 .get_mut(token as usize)
138 .ok_or_else(|| anyhow::anyhow!("token exceeds vocabulary"))?;
139 *count = count.saturating_add(1);
140
141 Ok(())
142 }
143
144 pub(crate) fn sample(&mut self, logits: &[f32]) -> Result<u32> {
145 ensure!(
146 logits.len() == self.counts.len(),
147 "logit vocabulary mismatch"
148 );
149 self.candidates.clear();
150
151 for (id, (&logit, &count)) in logits.iter().zip(&self.counts).enumerate() {
152 ensure!(
153 !logit.is_nan() && logit != f32::INFINITY,
154 "non-finite model logits"
155 );
156
157 let penalty = self.options.frequency_penalty * f64::from(count)
158 + if count > 0 {
159 self.options.presence_penalty
160 } else {
161 0.0
162 };
163
164 self.candidates
165 .push((id as u32, f64::from(logit) - penalty));
166 }
167
168 let best = self
169 .candidates
170 .iter()
171 .min_by(|a, b| rank(a, b))
172 .copied()
173 .ok_or_else(|| anyhow::anyhow!("empty vocabulary"))?;
174
175 ensure!(best.1.is_finite(), "no finite candidate logits");
176
177 if self.options.temperature == 0.0 {
178 return Ok(best.0);
179 }
180
181 let k = self.options.top_k;
182
183 if k > 0 && k < self.candidates.len() {
184 self.candidates.select_nth_unstable_by(k, rank);
185 self.candidates.truncate(k);
186 }
187
188 if self.options.top_p < 1.0 {
189 self.candidates.sort_unstable_by(rank);
190 }
191
192 let mut total = 0.0;
193
194 for (_, weight) in &mut self.candidates {
195 *weight = ((*weight - best.1) / self.options.temperature).exp();
196 total += *weight;
197 }
198
199 let (keep, mass) = nucleus(&self.candidates, total * self.options.top_p);
200 let draw = self.rng.random::<f64>() * mass;
201 let mut cumulative = 0.0;
202
203 for &(token, weight) in &self.candidates[..keep] {
204 cumulative += weight;
205
206 if draw < cumulative {
207 return Ok(token);
208 }
209 }
210
211 Ok(self.candidates[keep - 1].0)
212 }
213}
214
215fn rank(a: &(u32, f64), b: &(u32, f64)) -> std::cmp::Ordering {
216 b.1.total_cmp(&a.1).then_with(|| a.0.cmp(&b.0))
217}
218
219fn nucleus(candidates: &[(u32, f64)], threshold: f64) -> (usize, f64) {
220 let mut mass = 0.0;
221
222 for (i, &(_, weight)) in candidates.iter().enumerate() {
223 mass += weight;
224
225 if mass >= threshold {
226 return (i + 1, mass);
227 }
228 }
229
230 (candidates.len(), mass)
231}
232
233#[cfg(test)]
234#[path = "../tests/unit/sampling.rs"]
235mod tests;