use candle_core::DType;
use candle_core::Device;
use candle_core::Tensor;
use crate::error::CanddingError;
use crate::error::Result;
pub fn mask_min(dtype: DType) -> f64 {
match dtype {
DType::F16 => -65504.0,
DType::BF16 => -3.3895e38,
_ => f32::MIN as f64,
}
}
pub struct AttentionMask {
additive: Option<Tensor>,
causal: bool,
}
impl AttentionMask {
pub fn none() -> Self {
Self {
additive: None,
causal: false,
}
}
pub fn additive(&self) -> Option<&Tensor> {
self.additive.as_ref()
}
pub fn is_causal(&self) -> bool {
self.causal
}
pub fn padding(
attention_mask: &Tensor,
lengths: &[usize],
num_heads: usize,
head_dim: usize,
dtype: DType,
) -> Result<Self> {
let (batch, seq) = attention_mask.dims2()?;
check_lengths(lengths, batch)?;
if lengths.iter().all(|&len| len == seq) {
return Ok(Self::none());
}
let inverted = attention_mask.to_dtype(DType::F32)?.affine(-1.0, 1.0)?;
let mask = inverted
.affine(mask_min(dtype), 0.0)?
.to_dtype(dtype)?
.reshape((batch, 1, 1, seq))?;
Ok(Self {
additive: Some(broadcast_for_device(mask, batch, num_heads, head_dim, seq)?),
causal: false,
})
}
pub fn padding_with_bias(
attention_mask: &Tensor,
lengths: &[usize],
bias: &Tensor,
num_heads: usize,
head_dim: usize,
dtype: DType,
) -> Result<Self> {
let (batch, seq) = attention_mask.dims2()?;
check_lengths(lengths, batch)?;
let combined = if lengths.iter().all(|&len| len == seq) {
bias.to_dtype(dtype)?
} else {
let keep = attention_mask
.to_dtype(DType::F32)?
.reshape((batch, 1, 1, seq))?;
let floor = keep.affine(-1.0, 1.0)?.affine(mask_min(dtype), 0.0)?;
bias
.broadcast_mul(&keep)?
.broadcast_add(&floor)?
.to_dtype(dtype)?
};
Ok(Self {
additive: Some(broadcast_for_device(
combined, batch, num_heads, head_dim, seq,
)?),
causal: false,
})
}
pub fn causal(seq: usize, head_dim: usize, dtype: DType, device: &Device) -> Result<Self> {
if crate::layers::attention::metal_sdpa_applies(device, head_dim, dtype) {
return Ok(Self {
additive: None,
causal: true,
});
}
let floor = mask_min(dtype) as f32;
let values: Vec<f32> = (0..seq)
.flat_map(|i| (0..seq).map(move |j| if j > i { floor } else { 0.0 }))
.collect();
let mask = Tensor::from_vec(values, (1, 1, seq, seq), device)?.to_dtype(dtype)?;
Ok(Self {
additive: Some(mask),
causal: false,
})
}
pub fn sliding(
attention_mask: &Tensor,
lengths: &[usize],
window: usize,
num_heads: usize,
head_dim: usize,
dtype: DType,
) -> Result<Self> {
let (batch, seq) = attention_mask.dims2()?;
check_lengths(lengths, batch)?;
if seq <= window && lengths.iter().all(|&len| len == seq) {
return Ok(Self::none());
}
let device = attention_mask.device();
let positions = Tensor::arange(0u32, seq as u32, device)?.to_dtype(DType::F32)?;
let distance = positions
.reshape((seq, 1))?
.broadcast_sub(&positions.reshape((1, seq))?)?
.abs()?;
let band = distance.lt(window as f32)?.reshape((1, 1, seq, seq))?;
let keys = attention_mask
.to_dtype(DType::U8)?
.reshape((batch, 1, 1, seq))?;
let own_key = Tensor::eye(seq, DType::U8, device)?.reshape((1, 1, seq, seq))?;
let visible = band.broadcast_mul(&keys)?.broadcast_maximum(&own_key)?;
let mask = visible
.to_dtype(DType::F32)?
.affine(-1.0, 1.0)?
.affine(mask_min(dtype), 0.0)?
.to_dtype(dtype)?;
Ok(Self {
additive: Some(broadcast_for_device(mask, batch, num_heads, head_dim, seq)?),
causal: false,
})
}
}
pub(crate) fn check_lengths(lengths: &[usize], batch: usize) -> Result<()> {
if lengths.len() != batch {
return Err(CanddingError::LengthsMismatch {
lengths: lengths.len(),
rows: batch,
});
}
Ok(())
}
fn broadcast_for_device(
mask: Tensor,
batch: usize,
num_heads: usize,
head_dim: usize,
seq: usize,
) -> Result<Tensor> {
if crate::layers::attention::metal_sdpa_applies(mask.device(), head_dim, mask.dtype()) {
return Ok(mask.broadcast_as((batch, num_heads, seq, seq))?);
}
Ok(mask)
}
#[cfg(test)]
mod tests;