use fuel_ir::error::Result;
use fuel_tensor::lazy::Tensor;
use rand::distr::Distribution;
use rand::rngs::StdRng;
fn realize_logits(logits: &Tensor) -> Result<Vec<f32>> {
let device = fuel_tensor::Device::cpu();
let v = fuel_tensor::pipelined_bridge::realize_one_as::<f32>(
logits.graph(),
logits.node_id(),
&device,
)?;
if v.is_empty() {
return Err(fuel_ir::error::Error::Msg(
"sampling: realized logits vector is empty".to_string(),
)
.bt());
}
Ok(v)
}
fn argmax(values: &[f32]) -> usize {
let mut best_i = 0usize;
let mut best_v = values[0];
for (i, &v) in values.iter().enumerate().skip(1) {
if v.total_cmp(&best_v).is_gt() {
best_v = v;
best_i = i;
}
}
best_i
}
fn softmax_with_temp(mut logits: Vec<f32>, temp: f32) -> Result<Vec<f32>> {
if !(temp.is_finite()) || temp <= 0.0 {
return Err(fuel_ir::error::Error::Msg(format!(
"sampling: temperature must be > 0 and finite, got {temp}",
))
.bt());
}
for v in logits.iter_mut() {
*v /= temp;
}
let mut max_v = logits[0];
for &v in logits.iter().skip(1) {
if v.total_cmp(&max_v).is_gt() {
max_v = v;
}
}
let mut sum = 0.0_f32;
for v in logits.iter_mut() {
*v = (*v - max_v).exp();
sum += *v;
}
if !sum.is_finite() || sum <= 0.0 {
return Err(fuel_ir::error::Error::Msg(format!(
"sampling: softmax sum is non-positive or non-finite ({sum})",
))
.bt());
}
for v in logits.iter_mut() {
*v /= sum;
}
Ok(logits)
}
fn sample_multinomial(prs: &[f32], rng: &mut StdRng) -> Result<usize> {
let distr =
rand::distr::weighted::WeightedIndex::new(prs).map_err(fuel_ir::error::Error::wrap)?;
Ok(distr.sample(rng))
}
pub fn greedy(logits: &Tensor) -> Result<u32> {
let values = realize_logits(logits)?;
let idx = argmax(&values);
Ok(idx as u32)
}
pub fn temperature_sample(logits: &Tensor, temp: f32, rng: &mut StdRng) -> Result<u32> {
let values = realize_logits(logits)?;
let prs = softmax_with_temp(values, temp)?;
let idx = sample_multinomial(&prs, rng)?;
Ok(idx as u32)
}
pub fn top_k_sample(logits: &Tensor, k: usize, temp: f32, rng: &mut StdRng) -> Result<u32> {
let values = realize_logits(logits)?;
let prs = softmax_with_temp(values, temp)?;
if k == 0 {
return Err(fuel_ir::error::Error::Msg(
"sampling: top_k_sample requires k >= 1".to_string(),
)
.bt());
}
let k = k.min(prs.len());
let mut idxs: Vec<usize> = (0..prs.len()).collect();
let (left, &mut pivot_idx, _) =
idxs.select_nth_unstable_by(k - 1, |&i, &j| prs[j].total_cmp(&prs[i]));
let mut top: Vec<usize> = left.to_vec();
top.push(pivot_idx);
let restricted: Vec<f32> = top.iter().map(|&i| prs[i]).collect();
let pick_local = sample_multinomial(&restricted, rng)?;
Ok(top[pick_local] as u32)
}
pub fn top_p_sample(logits: &Tensor, p: f32, temp: f32, rng: &mut StdRng) -> Result<u32> {
let values = realize_logits(logits)?;
let mut prs = softmax_with_temp(values, temp)?;
if !p.is_finite() {
return Err(fuel_ir::error::Error::Msg(
format!("sampling: top_p must be finite, got {p}",),
)
.bt());
}
if p <= 0.0 {
let idx = argmax(&prs);
return Ok(idx as u32);
}
if p >= 1.0 {
let idx = sample_multinomial(&prs, rng)?;
return Ok(idx as u32);
}
let mut order: Vec<usize> = (0..prs.len()).collect();
order.sort_by(|&i, &j| prs[j].total_cmp(&prs[i]));
let mut cumsum = 0.0_f32;
for &index in &order {
if cumsum >= p {
prs[index] = 0.0;
} else {
cumsum += prs[index];
}
}
let idx = sample_multinomial(&prs, rng)?;
Ok(idx as u32)
}
#[cfg(test)]
mod tests {
use super::*;
use fuel_ir::Shape;
use fuel_tensor::Device;
use rand::SeedableRng;
fn lazy_logits(values: Vec<f32>) -> Tensor {
let n = values.len();
Tensor::from_f32(values, Shape::from_dims(&[n]), &Device::cpu()).unwrap()
}
#[test]
fn greedy_picks_max_index() {
let logits = lazy_logits(vec![0.1, 5.0, 0.2, 0.3, -1.0]);
let idx = greedy(&logits).unwrap();
assert_eq!(idx, 1);
}
#[test]
fn temperature_sample_with_very_low_temp_picks_argmax() {
let logits = lazy_logits(vec![0.0, 0.0, 4.0, 0.0]);
let mut rng = StdRng::seed_from_u64(0xdeadbeef);
let idx = temperature_sample(&logits, 1e-4, &mut rng).unwrap();
assert_eq!(idx, 2);
}
#[test]
fn top_k_sample_only_picks_from_top_k_indices() {
let logits = lazy_logits(vec![-2.0, 3.0, -1.5, 2.5, -1.0, 2.0, -0.5, -3.0]);
let mut rng = StdRng::seed_from_u64(7);
let allowed: [u32; 3] = [1, 3, 5];
for _ in 0..200 {
let idx = top_k_sample(&logits, 3, 1.0, &mut rng).unwrap();
assert!(
allowed.contains(&idx),
"top_k_sample returned {idx}, not in {allowed:?}",
);
}
}
#[test]
fn top_p_sample_with_p_1_equals_full_distribution() {
let logits = lazy_logits(vec![0.5, 1.5, -0.5, 2.0, 0.25]);
let mut rng_a = StdRng::seed_from_u64(42);
let mut rng_b = StdRng::seed_from_u64(42);
for _ in 0..32 {
let a = top_p_sample(&logits, 1.0, 1.0, &mut rng_a).unwrap();
let b = temperature_sample(&logits, 1.0, &mut rng_b).unwrap();
assert_eq!(a, b);
}
}
}