use crate::errors::{Result, TrustformersError};
use crate::tensor::Tensor;
use scirs2_core::ndarray::ArrayD;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MaskSemantics {
Keep,
Additive,
}
impl MaskSemantics {
pub fn detect(values: &ArrayD<f32>) -> Self {
let mut saw_one = false;
for &value in values.iter() {
if value == 1.0 {
saw_one = true;
} else if value != 0.0 {
return MaskSemantics::Additive;
}
}
if saw_one {
MaskSemantics::Keep
} else {
MaskSemantics::Additive
}
}
}
#[derive(Debug, Clone, Copy)]
enum MaskKind {
QueryKey,
BatchKey,
BatchQueryKey {
broadcast_batch: bool,
broadcast_query: bool,
},
Full {
broadcast_batch: bool,
broadcast_head: bool,
broadcast_query: bool,
},
}
pub(crate) struct MaskView<'a> {
values: &'a ArrayD<f32>,
semantics: MaskSemantics,
kind: MaskKind,
}
impl<'a> MaskView<'a> {
pub(crate) fn new(
mask: &'a Tensor,
batch: usize,
heads: usize,
seq_q: usize,
seq_k: usize,
) -> Result<Self> {
let values = match mask {
Tensor::F32(array) => array,
other => {
return Err(TrustformersError::tensor_op_error(
&format!(
"Attention masks must be F32 tensors, got {:?}",
other.dtype()
),
"MaskView::new",
));
},
};
let shape = values.shape();
let kind = match shape.len() {
2 => {
if shape[0] == seq_q && shape[1] == seq_k {
MaskKind::QueryKey
} else if shape[0] == batch && shape[1] == seq_k {
MaskKind::BatchKey
} else {
return Err(Self::shape_error(shape, batch, heads, seq_q, seq_k));
}
},
3 => {
let batch_ok = shape[0] == batch || shape[0] == 1;
let query_ok = shape[1] == seq_q || shape[1] == 1;
if !batch_ok || !query_ok || shape[2] != seq_k {
return Err(Self::shape_error(shape, batch, heads, seq_q, seq_k));
}
MaskKind::BatchQueryKey {
broadcast_batch: shape[0] == 1 && batch != 1,
broadcast_query: shape[1] == 1 && seq_q != 1,
}
},
4 => {
let batch_ok = shape[0] == batch || shape[0] == 1;
let head_ok = shape[1] == heads || shape[1] == 1;
let query_ok = shape[2] == seq_q || shape[2] == 1;
if !batch_ok || !head_ok || !query_ok || shape[3] != seq_k {
return Err(Self::shape_error(shape, batch, heads, seq_q, seq_k));
}
MaskKind::Full {
broadcast_batch: shape[0] == 1 && batch != 1,
broadcast_head: shape[1] == 1 && heads != 1,
broadcast_query: shape[2] == 1 && seq_q != 1,
}
},
_ => return Err(Self::shape_error(shape, batch, heads, seq_q, seq_k)),
};
Ok(Self {
values,
semantics: MaskSemantics::detect(values),
kind,
})
}
fn shape_error(
shape: &[usize],
batch: usize,
heads: usize,
seq_q: usize,
seq_k: usize,
) -> TrustformersError {
TrustformersError::tensor_op_error(
&format!(
"Attention mask shape {:?} is not broadcastable to [{}, {}, {}, {}]",
shape, batch, heads, seq_q, seq_k
),
"MaskView::new",
)
}
#[inline]
fn raw(&self, batch: usize, head: usize, query: usize, key: usize) -> f32 {
match self.kind {
MaskKind::QueryKey => self.values[[query, key]],
MaskKind::BatchKey => self.values[[batch, key]],
MaskKind::BatchQueryKey {
broadcast_batch,
broadcast_query,
} => {
let b = if broadcast_batch { 0 } else { batch };
let q = if broadcast_query { 0 } else { query };
self.values[[b, q, key]]
},
MaskKind::Full {
broadcast_batch,
broadcast_head,
broadcast_query,
} => {
let b = if broadcast_batch { 0 } else { batch };
let h = if broadcast_head { 0 } else { head };
let q = if broadcast_query { 0 } else { query };
self.values[[b, h, q, key]]
},
}
}
#[inline]
pub(crate) fn additive(&self, batch: usize, head: usize, query: usize, key: usize) -> f32 {
let value = self.raw(batch, head, query, key);
match self.semantics {
MaskSemantics::Keep => {
if value == 0.0 {
f32::NEG_INFINITY
} else {
0.0
}
},
MaskSemantics::Additive => value,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::IxDyn;
fn tensor(data: Vec<f32>, shape: &[usize]) -> Tensor {
Tensor::F32(
ArrayD::from_shape_vec(IxDyn(shape), data).expect("test mask shape must be valid"),
)
}
#[test]
fn detects_keep_masks() {
let mask = tensor(vec![1.0, 0.0, 1.0, 1.0], &[2, 2]);
let view = MaskView::new(&mask, 1, 1, 2, 2).expect("2-D keep mask must be accepted");
assert_eq!(view.semantics, MaskSemantics::Keep);
assert_eq!(view.additive(0, 0, 0, 0), 0.0);
assert_eq!(view.additive(0, 0, 0, 1), f32::NEG_INFINITY);
}
#[test]
fn detects_additive_masks() {
let mask = tensor(vec![0.0, f32::NEG_INFINITY, 0.0, 0.0], &[2, 2]);
let view = MaskView::new(&mask, 1, 1, 2, 2).expect("2-D additive mask must be accepted");
assert_eq!(view.semantics, MaskSemantics::Additive);
assert_eq!(view.additive(0, 0, 0, 0), 0.0);
assert_eq!(view.additive(0, 0, 0, 1), f32::NEG_INFINITY);
}
#[test]
fn all_zero_mask_is_treated_as_additive_no_op() {
let mask = tensor(vec![0.0; 4], &[2, 2]);
let view = MaskView::new(&mask, 1, 1, 2, 2).expect("all-zero mask must be accepted");
assert_eq!(view.semantics, MaskSemantics::Additive);
assert_eq!(view.additive(0, 0, 1, 1), 0.0);
}
#[test]
fn broadcasts_padding_masks() {
let mask = tensor(vec![1.0, 1.0, 0.0], &[1, 1, 1, 3]);
let view = MaskView::new(&mask, 2, 4, 3, 3).expect("padding mask must broadcast");
assert_eq!(view.semantics, MaskSemantics::Keep);
assert_eq!(view.additive(1, 3, 2, 0), 0.0);
assert_eq!(view.additive(1, 3, 2, 2), f32::NEG_INFINITY);
}
#[test]
fn rejects_incompatible_shapes() {
let mask = tensor(vec![1.0; 6], &[2, 3]);
assert!(MaskView::new(&mask, 4, 2, 5, 5).is_err());
}
}