use std::{path::Path, sync::Arc};
use half::{f16, slice::HalfFloatSliceExt};
use super::{
super::{
alloc::reserve_exact,
artifacts::{
ArtifactLabel, Part,
stamp::{PartView, Stamp},
},
audio::AudioPrecision,
config::{self, POOLING_KERNEL},
error::{AllocationSite, BudgetNotLoaded, Error, ResizeFailure, Result},
options::ImageOptions,
table::PositionTable,
},
VisionBudget,
};
use crate::{
DataType, MultiArray,
embeddings::image::{ImageDimensions, Rgb8Image},
};
pub(crate) const MAX_IMAGE_AXIS: usize = 1 << 20;
const PATCH: usize = config::PATCH_SIZE;
pub(crate) const PATCH_VALUES: usize = PATCH * PATCH * 3;
const SIDE: usize = POOLING_KERNEL * PATCH;
pub(crate) const POSITION_WIDTH: usize = 768;
pub(crate) static RESCALE: [f16; 256] = {
let mut table = [f16::ZERO; 256];
let mut v = 0;
while v < 256 {
table[v] = f16::from_f64_const(v as f64 / 255.0);
v += 1;
}
table
};
pub(crate) fn target_size(
height: usize,
width: usize,
budget: VisionBudget,
) -> Result<(usize, usize)> {
let refuse = || Error::ImageDimensions(ImageDimensions::new(width, height));
let max_patches = budget.patches();
let target_px = max_patches * PATCH * PATCH;
let factor = (target_px as f64 / (height as f64 * width as f64)).sqrt();
let ideal_height = factor * height as f64;
let ideal_width = factor * width as f64;
let mut target_height = (ideal_height / SIDE as f64).floor() as usize * SIDE;
let mut target_width = (ideal_width / SIDE as f64).floor() as usize * SIDE;
if target_height == 0 && target_width == 0 {
return Err(refuse());
}
let max_side = (max_patches / (POOLING_KERNEL * POOLING_KERNEL)) * SIDE;
if target_height == 0 {
target_height = SIDE;
target_width = ((width as f64 / height as f64).floor() as usize * SIDE).min(max_side);
} else if target_width == 0 {
target_width = SIDE;
target_height = ((height as f64 / width as f64).floor() as usize * SIDE).min(max_side);
}
if target_height * target_width > target_px {
return Err(refuse());
}
Ok((target_height, target_width))
}
#[derive(Debug)]
pub struct PreparedImage {
budget: Option<VisionBudget>,
shaped: Option<VisionBudget>,
real: usize,
patches: Option<MultiArray>,
positions: Option<MultiArray>,
valid: Option<MultiArray>,
pool: Option<MultiArray>,
position_embeddings: Option<MultiArray>,
resized: Vec<u8>,
}
impl Default for PreparedImage {
fn default() -> Self {
Self::new()
}
}
impl PreparedImage {
pub const fn new() -> Self {
Self {
budget: None,
shaped: None,
real: 0,
patches: None,
positions: None,
valid: None,
pool: None,
position_embeddings: None,
resized: Vec::new(),
}
}
#[inline]
pub const fn budget(&self) -> Option<VisionBudget> {
self.budget
}
#[inline]
pub const fn patches(&self) -> usize {
self.real
}
#[inline]
pub const fn tokens(&self) -> usize {
self.real / (POOLING_KERNEL * POOLING_KERNEL)
}
pub(crate) fn inputs(&self) -> Option<[(&'static str, &MultiArray); 5]> {
Some([
("patches", self.patches.as_ref()?),
("pool", self.pool.as_ref()?),
("position_embeddings", self.position_embeddings.as_ref()?),
("positions", self.positions.as_ref()?),
("valid", self.valid.as_ref()?),
])
}
fn shape_for(&mut self, budget: VisionBudget) -> Result<()> {
if self.shaped == Some(budget) {
return Ok(());
}
self.shaped = None;
let (n, t) = (budget.patches(), budget.tokens());
let zeros = |shape: &[usize]| MultiArray::zeros(shape, DataType::F16);
self.patches = Some(zeros(&[1, n, PATCH_VALUES])?);
self.positions = Some(zeros(&[1, n, 2])?);
self.valid = Some(zeros(&[1, n])?);
self.pool = Some(zeros(&[t, n])?);
self.position_embeddings = Some(zeros(&[1, n, POSITION_WIDTH])?);
self.shaped = Some(budget);
Ok(())
}
}
#[derive(Debug)]
pub(crate) struct Shared {
pub(crate) view: PartView,
table: PositionTable,
}
#[derive(Debug, Clone)]
pub struct VisionArtifacts {
pub(crate) shared: Arc<Shared>,
}
impl VisionArtifacts {
pub fn open(dir: impl AsRef<Path>) -> Result<Self> {
let dir = dir.as_ref();
let view = Stamp::read(dir)?.part(dir, Part::Vision, AudioPrecision::Fp16)?;
config::load(&dir.join("config.json"))?;
let table = PositionTable::open(&dir.join("position_embeddings.f16"), view.sentinels())?;
Ok(Self {
shared: Arc::new(Shared { view, table }),
})
}
#[inline]
pub fn artifact_label(&self) -> ArtifactLabel {
self.shared.view.label()
}
pub fn prepare(
&self,
image: Rgb8Image<'_>,
budget: Option<VisionBudget>,
options: &ImageOptions,
) -> Result<PreparedImage> {
let mut out = PreparedImage::new();
self.prepare_into(image, budget, options, &mut out)?;
Ok(out)
}
pub fn prepare_into(
&self,
image: Rgb8Image<'_>,
budget: Option<VisionBudget>,
options: &ImageOptions,
out: &mut PreparedImage,
) -> Result<()> {
out.budget = None;
out.real = 0;
let budget = budget.unwrap_or(options.default_budget());
if !options.budgets().contains(budget) {
return Err(Error::BudgetNotLoaded(BudgetNotLoaded::new(
budget,
options.budgets(),
)));
}
let (height, width) = (image.height(), image.width());
if height > MAX_IMAGE_AXIS || width > MAX_IMAGE_AXIS {
return Err(Error::ImageDimensions(ImageDimensions::new(width, height)));
}
let (target_height, target_width) = target_size(height, width, budget)?;
out.shape_for(budget)?;
let PreparedImage {
real,
patches,
positions,
valid,
pool,
position_embeddings,
resized,
..
} = out;
let pixels: &[u8] = if (target_height, target_width) == (height, width) {
image.data()
} else {
resize(image, target_height, target_width, resized)?;
&resized[..]
};
let (rows, cols) = (target_height / PATCH, target_width / PATCH);
*real = rows * cols;
let planes = Planes {
patches: patches.as_mut().expect("shaped").as_slice_mut::<f16>()?,
positions: positions.as_mut().expect("shaped").as_slice_mut::<f16>()?,
valid: valid.as_mut().expect("shaped").as_slice_mut::<f16>()?,
pool: pool.as_mut().expect("shaped").as_slice_mut::<f16>()?,
position_embeddings: position_embeddings
.as_mut()
.expect("shaped")
.as_slice_mut::<f16>()?,
};
fill(pixels, rows, cols, &self.shared.table, planes);
out.budget = Some(budget);
Ok(())
}
}
fn resize(image: Rgb8Image<'_>, height: usize, width: usize, out: &mut Vec<u8>) -> Result<()> {
use pixon::{Convert, frame::Rgb24Frame, resample::CatmullRom};
let len = height * width * 3;
out.clear();
reserve_exact(out, len, AllocationSite::ImagePlanes)?;
out.resize(len, 0);
let (w, h) = (image.width() as u32, image.height() as u32);
let frame = Rgb24Frame::try_new(image.data(), w, h, w * 3)
.map_err(|_| Error::ImageDimensions(ImageDimensions::new(image.width(), image.height())))?;
Convert::from(&frame)
.resize_with(CatmullRom, width, height)
.rgb(out)
.run()
.map_err(|e| Error::Resize(ResizeFailure::new(e)))
}
pub(crate) struct Planes<'a> {
pub(crate) patches: &'a mut [f16],
pub(crate) positions: &'a mut [f16],
pub(crate) valid: &'a mut [f16],
pub(crate) pool: &'a mut [f16],
pub(crate) position_embeddings: &'a mut [f16],
}
pub(crate) fn fill(
pixels: &[u8],
rows: usize,
cols: usize,
table: &PositionTable,
planes: Planes<'_>,
) {
let width = cols * PATCH * 3;
let real = rows * cols;
let groups_per_row = cols / POOLING_KERNEL;
let ninth = f16::from_f64_const(1.0 / 9.0);
let n = planes.valid.len();
planes.pool.fill(f16::ZERO);
planes.patches[real * PATCH_VALUES..].fill(f16::ZERO);
planes.position_embeddings[real * POSITION_WIDTH..].fill(f16::ZERO);
planes.positions[real * 2..].fill(f16::from_f32_const(-1.0));
planes.valid[..real].fill(f16::ONE);
planes.valid[real..].fill(f16::ZERO);
let (mut x_row, mut y_row) = ([0f32; POSITION_WIDTH], [0f32; POSITION_WIDTH]);
let mut halves = [f16::ZERO; POSITION_WIDTH];
for row in 0..rows {
decode_row(
table.row(1, row).expect("a row inside the table"),
&mut halves,
);
halves.convert_to_f32_slice(&mut y_row);
for col in 0..cols {
let p = row * cols + col;
let patch = &mut planes.patches[p * PATCH_VALUES..(p + 1) * PATCH_VALUES];
for y in 0..PATCH {
let start = (row * PATCH + y) * width + col * PATCH * 3;
let source = &pixels[start..start + PATCH * 3];
for (cell, &value) in patch[y * PATCH * 3..(y + 1) * PATCH * 3]
.iter_mut()
.zip(source)
{
*cell = RESCALE[usize::from(value)];
}
}
planes.positions[p * 2] = f16::from_f32(col as f32);
planes.positions[p * 2 + 1] = f16::from_f32(row as f32);
let group = col / POOLING_KERNEL + groups_per_row * (row / POOLING_KERNEL);
planes.pool[group * n + p] = ninth;
decode_row(
table.row(0, col).expect("a column inside the table"),
&mut halves,
);
halves.convert_to_f32_slice(&mut x_row);
for (x, y) in x_row.iter_mut().zip(&y_row) {
*x += *y;
}
planes.position_embeddings[p * POSITION_WIDTH..(p + 1) * POSITION_WIDTH]
.convert_from_f32_slice(&x_row);
}
}
}
fn decode_row(bytes: &[u8], out: &mut [f16; POSITION_WIDTH]) {
for (half, pair) in out.iter_mut().zip(bytes.as_chunks::<2>().0) {
*half = f16::from_bits(u16::from_le_bytes(*pair));
}
}
#[cfg(test)]
mod tests;