use half::f16;
use super::{
super::{
alloc,
config::{BOS_ID, EOS_ID, HIDDEN_SIZE, PACK_SLOTS},
error::AllocationSite,
options::Batching,
table::{GatherRefusal, ShapeRefusal, TokenTable},
tokenizer::bpe::TokenId,
},
TextFunction,
};
pub(crate) const PACK_ROWS: usize = 256;
pub(crate) const MASKED: f16 = f16::from_f32_const(-10_000.0);
#[inline]
pub(crate) const fn unpacked(n: usize) -> TextFunction {
if n <= 32 {
TextFunction::Embed32
} else if n <= 48 {
TextFunction::Embed48
} else if n <= 64 {
TextFunction::Embed64
} else if n <= 128 {
TextFunction::Embed128
} else if n <= 256 {
TextFunction::Embed256
} else {
TextFunction::Embed512
}
}
#[inline]
pub(crate) const fn choose(n: usize, batching: Batching) -> TextFunction {
match batching {
Batching::Packed if n <= PACK_ROWS => TextFunction::Pack256,
_ => unpacked(n),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Pack {
members: Vec<usize>,
rows: usize,
}
impl Pack {
#[inline]
pub(crate) fn members(&self) -> &[usize] {
&self.members
}
#[cfg(test)]
pub(crate) const fn rows(&self) -> usize {
self.rows
}
}
pub(crate) fn plan_packs(
lengths: &[usize],
) -> super::super::error::Result<(Vec<Pack>, Vec<usize>)> {
let mut packs: Vec<Pack> = alloc::with_capacity(lengths.len(), AllocationSite::PackLayout)?;
let mut alone = alloc::with_capacity(lengths.len(), AllocationSite::PackLayout)?;
for (index, &length) in lengths.iter().enumerate() {
if length > PACK_ROWS {
alone.push(index);
continue;
}
match packs
.iter_mut()
.find(|pack| pack.members.len() < PACK_SLOTS && pack.rows + length <= PACK_ROWS)
{
Some(pack) => {
pack.members.push(index);
pack.rows += length;
}
None => {
let mut members = alloc::with_capacity(PACK_SLOTS, AllocationSite::PackLayout)?;
members.push(index);
packs.push(Pack {
members,
rows: length,
});
}
}
}
Ok((packs, alone))
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum Sequence<'a> {
Text(&'a [TokenId]),
Media(MediaSequence<'a>),
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct MediaSequence<'a> {
pub(crate) open: TokenId,
pub(crate) rows: &'a [f16],
pub(crate) close: TokenId,
}
impl Sequence<'_> {
#[inline]
pub(crate) const fn len(&self) -> usize {
match self {
Self::Text(ids) => ids.len(),
Self::Media(media) => media.rows.len() / HIDDEN_SIZE + 4,
}
}
fn write(&self, table: &TokenTable, out: &mut [f16]) -> Result<(), GatherRefusal> {
match self {
Self::Text(ids) => {
for (id, slot) in ids.iter().zip(out.as_chunks_mut::<HIDDEN_SIZE>().0) {
table.gather_id(*id, slot)?;
}
}
Self::Media(MediaSequence { open, rows, close }) => {
let tokens = rows.len() / HIDDEN_SIZE;
let (head, rest) = out.split_at_mut(2 * HIDDEN_SIZE);
let (bos, start) = head.split_at_mut(HIDDEN_SIZE);
table.gather_id(TokenId::from_vocab(BOS_ID), bos)?;
table.gather_id(*open, start)?;
let (body, tail) = rest.split_at_mut(tokens * HIDDEN_SIZE);
body.copy_from_slice(&rows[..tokens * HIDDEN_SIZE]);
let (end, eos) = tail.split_at_mut(HIDDEN_SIZE);
table.gather_id(*close, end)?;
table.gather_id(TokenId::from_vocab(EOS_ID), &mut eos[..HIDDEN_SIZE])?;
}
}
Ok(())
}
}
pub(crate) fn fill_unpacked(
table: &TokenTable,
sequence: &Sequence<'_>,
inputs_embeds: &mut [f16],
mask: &mut [f16],
) -> Result<(), GatherRefusal> {
let n = sequence.len();
if inputs_embeds.len() != mask.len() * HIDDEN_SIZE || n > mask.len() {
return Err(GatherRefusal::Shape(ShapeRefusal {
ids: n,
cells: inputs_embeds.len(),
}));
}
match sequence {
Sequence::Text(ids) => table.gather_sequence(ids, inputs_embeds)?,
Sequence::Media(_) => {
sequence.write(table, inputs_embeds)?;
table.gather_pad(&mut inputs_embeds[n * HIDDEN_SIZE..])?;
}
}
mask[..n].fill(f16::ONE);
mask[n..].fill(f16::ZERO);
Ok(())
}
pub(crate) struct PackedInputs<'b> {
pub(crate) attention_bias: &'b mut [f16],
pub(crate) inputs_embeds: &'b mut [f16],
pub(crate) pool: &'b mut [f16],
pub(crate) positions: &'b mut [f16],
}
pub(crate) fn fill_packed(
table: &TokenTable,
sequences: &[Sequence<'_>],
inputs: PackedInputs<'_>,
) -> Result<(), GatherRefusal> {
let PackedInputs {
attention_bias,
inputs_embeds,
pool,
positions,
} = inputs;
let total: usize = sequences.iter().map(Sequence::len).sum();
if sequences.len() > PACK_SLOTS
|| total > PACK_ROWS
|| attention_bias.len() != PACK_ROWS * PACK_ROWS
|| inputs_embeds.len() != PACK_ROWS * HIDDEN_SIZE
|| pool.len() != PACK_SLOTS * PACK_ROWS
|| positions.len() != PACK_ROWS
{
return Err(GatherRefusal::Shape(ShapeRefusal {
ids: total,
cells: inputs_embeds.len(),
}));
}
attention_bias.fill(MASKED);
pool.fill(f16::ZERO);
positions.fill(f16::ZERO);
let mut offset = 0;
for (slot, sequence) in sequences.iter().enumerate() {
let n = sequence.len();
sequence.write(table, &mut inputs_embeds[offset * HIDDEN_SIZE..])?;
let weight = f16::from_f32(1.0 / n as f32);
for row in offset..offset + n {
attention_bias[row * PACK_ROWS + offset..row * PACK_ROWS + offset + n].fill(f16::ZERO);
positions[row] = f16::from_f32((row - offset) as f32);
pool[slot * PACK_ROWS + row] = weight;
}
offset += n;
}
table.gather_pad(&mut inputs_embeds[offset * HIDDEN_SIZE..])?;
for row in offset..PACK_ROWS {
attention_bias[row * PACK_ROWS + row] = f16::ZERO;
}
Ok(())
}
#[cfg(test)]
mod tests;