use candle_core::{DType, Device, IndexOp, Tensor};
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use crate::backend::MIBackend;
use crate::error::Result;
use crate::hooks::HookSpec;
#[derive(Debug, Clone)]
pub struct DiffusionSamplingConfig {
pub seq_len: usize,
pub num_steps: usize,
pub temperature: f32,
pub top_k: Option<usize>,
pub seed: u64,
}
impl Default for DiffusionSamplingConfig {
fn default() -> Self {
Self {
seq_len: 64,
num_steps: 128,
temperature: 1.0,
top_k: None,
seed: 0,
}
}
}
pub fn generate(
model: &dyn MIBackend,
device: &Device,
mask_token_id: u32,
prompt_ids: &[u32],
config: &DiffusionSamplingConfig,
) -> Result<Vec<u32>> {
let mut trajectory = generate_trajectory(model, device, mask_token_id, prompt_ids, config)?;
Ok(trajectory.pop().unwrap_or_default())
}
pub fn generate_trajectory(
model: &dyn MIBackend,
device: &Device,
mask_token_id: u32,
prompt_ids: &[u32],
config: &DiffusionSamplingConfig,
) -> Result<Vec<Vec<u32>>> {
let seq_len = config.seq_len;
let mut rng = StdRng::seed_from_u64(config.seed);
let mut x = vec![mask_token_id; seq_len];
for (i, &token) in prompt_ids.iter().take(seq_len).enumerate() {
if let Some(slot) = x.get_mut(i) {
*slot = token;
}
}
let mut trajectory = Vec::with_capacity(config.num_steps + 1);
trajectory.push(x.clone());
let hooks = HookSpec::new();
for step in 0..config.num_steps {
#[allow(clippy::cast_precision_loss, clippy::as_conversions)]
let (t, s) = {
let n = config.num_steps as f64;
(1.0 - step as f64 / n, 1.0 - (step + 1) as f64 / n)
};
let unmask_prob = if t > 0.0 { (t - s) / t } else { 1.0 };
let input = Tensor::new(x.as_slice(), device)?.unsqueeze(0)?; let cache = model.forward(&input, &hooks)?;
let logits = cache
.output()
.i(0)?
.to_dtype(DType::F32)?
.to_vec2::<f32>()?;
for (pos, row) in logits.iter().enumerate() {
if x.get(pos).copied() != Some(mask_token_id) {
continue;
}
if rng.r#gen::<f64>() < unmask_prob {
let token = sample_token_from_logits(
row,
mask_token_id,
config.temperature,
config.top_k,
&mut rng,
);
if let Some(slot) = x.get_mut(pos) {
*slot = token;
}
}
}
trajectory.push(x.clone());
}
Ok(trajectory)
}
fn sample_token_from_logits(
row: &[f32],
mask_token_id: u32,
temperature: f32,
top_k: Option<usize>,
rng: &mut StdRng,
) -> u32 {
#[allow(clippy::cast_possible_truncation, clippy::as_conversions)]
let mask_idx = mask_token_id as usize;
let inv_temp = 1.0 / temperature.max(1e-6);
let mut logits: Vec<f32> = row
.iter()
.enumerate()
.map(|(i, &l)| {
if i == mask_idx {
f32::NEG_INFINITY
} else {
l * inv_temp
}
})
.collect();
if let Some(k) = top_k.filter(|&k| k >= 1 && k < logits.len()) {
let mut ranked = logits.clone();
ranked.select_nth_unstable_by(k - 1, |a, b| {
b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
});
let threshold = ranked.get(k - 1).copied().unwrap_or(f32::NEG_INFINITY);
for l in &mut logits {
if *l < threshold {
*l = f32::NEG_INFINITY;
}
}
}
let max = logits.iter().copied().fold(f32::NEG_INFINITY, f32::max);
let exps: Vec<f32> = logits.iter().map(|&l| (l - max).exp()).collect();
let sum: f32 = exps.iter().sum();
let target = rng.r#gen::<f32>() * sum;
let mut cumulative = 0.0_f32;
for (i, &e) in exps.iter().enumerate() {
cumulative += e;
if target < cumulative {
#[allow(clippy::cast_possible_truncation, clippy::as_conversions)]
return i as u32;
}
}
#[allow(clippy::cast_possible_truncation, clippy::as_conversions)]
{
exps.len().saturating_sub(1) as u32
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn subs_never_samples_mask() {
let mask = 7u32;
let mut row = vec![0.0f32; 8];
row[2] = 50.0; row[7] = 100.0; let mut rng = StdRng::seed_from_u64(0);
for _ in 0..32 {
assert_eq!(sample_token_from_logits(&row, mask, 1.0, None, &mut rng), 2);
}
}
#[test]
fn top_k_one_is_greedy_over_non_mask() {
let mask = 7u32;
let mut row = vec![0.0f32; 8];
row[5] = 10.0; row[3] = 9.0;
row[7] = 20.0; let mut rng = StdRng::seed_from_u64(123);
for _ in 0..16 {
assert_eq!(
sample_token_from_logits(&row, mask, 1.0, Some(1), &mut rng),
5
);
}
}
#[test]
fn deterministic_given_seed() {
let row = vec![0.1f32, 0.5, 0.2, 0.9, 0.3, 0.7, 0.4, 0.6];
let mask = 7u32;
let mut r1 = StdRng::seed_from_u64(7);
let mut r2 = StdRng::seed_from_u64(7);
assert_eq!(
sample_token_from_logits(&row, mask, 1.0, None, &mut r1),
sample_token_from_logits(&row, mask, 1.0, None, &mut r2)
);
}
}